Merge git://git.kernel.org/pub/scm/gitk/gitk
authorJunio C Hamano <junkio@cox.net>
Wed, 7 Jun 2006 06:24:27 +0000 (23:24 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 7 Jun 2006 06:24:27 +0000 (23:24 -0700)
* git://git.kernel.org/pub/scm/gitk/gitk:
  gitk: Re-read the descendent/ancestor tag & head info on update
  gitk: Show branch name(s) as well, if "show nearby tags" is enabled
  gitk: Show nearby tags
  gitk: Add a goto next/previous highlighted commit function
  gitk: Provide ability to highlight based on relationship to selected commit
  gitk: Fix bug in highlight stuff when no line is selected
  gitk: Move "pickaxe" find function to highlight facility
  gitk: Improve the text window search function
  gitk: First cut at a search function in the patch/file display window
  gitk: Highlight paths of interest in tree view as well
  gitk: Highlight entries in the file list as well
  gitk: Make a row of controls for controlling highlighting

521 files changed:
.gitignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
Documentation/.gitignore [new file with mode: 0644]
Documentation/Makefile [new file with mode: 0644]
Documentation/SubmittingPatches [new file with mode: 0644]
Documentation/asciidoc.conf [new file with mode: 0644]
Documentation/build-docdep.perl [new file with mode: 0755]
Documentation/callouts.xsl [new file with mode: 0644]
Documentation/config.txt [new file with mode: 0644]
Documentation/core-tutorial.txt [new file with mode: 0644]
Documentation/cvs-migration.txt [new file with mode: 0644]
Documentation/diff-format.txt [new file with mode: 0644]
Documentation/diff-options.txt [new file with mode: 0644]
Documentation/diffcore.txt [new file with mode: 0644]
Documentation/everyday.txt [new file with mode: 0644]
Documentation/fetch-options.txt [new file with mode: 0644]
Documentation/git-add.txt [new file with mode: 0644]
Documentation/git-am.txt [new file with mode: 0644]
Documentation/git-annotate.txt [new file with mode: 0644]
Documentation/git-apply.txt [new file with mode: 0644]
Documentation/git-applymbox.txt [new file with mode: 0644]
Documentation/git-applypatch.txt [new file with mode: 0644]
Documentation/git-archimport.txt [new file with mode: 0644]
Documentation/git-bisect.txt [new file with mode: 0644]
Documentation/git-blame.txt [new file with mode: 0644]
Documentation/git-branch.txt [new file with mode: 0644]
Documentation/git-cat-file.txt [new file with mode: 0644]
Documentation/git-check-ref-format.txt [new file with mode: 0644]
Documentation/git-checkout-index.txt [new file with mode: 0644]
Documentation/git-checkout.txt [new file with mode: 0644]
Documentation/git-cherry-pick.txt [new file with mode: 0644]
Documentation/git-cherry.txt [new file with mode: 0644]
Documentation/git-clean.txt [new file with mode: 0644]
Documentation/git-clone-pack.txt [new file with mode: 0644]
Documentation/git-clone.txt [new file with mode: 0644]
Documentation/git-commit-tree.txt [new file with mode: 0644]
Documentation/git-commit.txt [new file with mode: 0644]
Documentation/git-convert-objects.txt [new file with mode: 0644]
Documentation/git-count-objects.txt [new file with mode: 0644]
Documentation/git-cvsexportcommit.txt [new file with mode: 0644]
Documentation/git-cvsimport.txt [new file with mode: 0644]
Documentation/git-cvsserver.txt [new file with mode: 0644]
Documentation/git-daemon.txt [new file with mode: 0644]
Documentation/git-describe.txt [new file with mode: 0644]
Documentation/git-diff-files.txt [new file with mode: 0644]
Documentation/git-diff-index.txt [new file with mode: 0644]
Documentation/git-diff-stages.txt [new file with mode: 0644]
Documentation/git-diff-tree.txt [new file with mode: 0644]
Documentation/git-diff.txt [new file with mode: 0644]
Documentation/git-fetch-pack.txt [new file with mode: 0644]
Documentation/git-fetch.txt [new file with mode: 0644]
Documentation/git-fmt-merge-msg.txt [new file with mode: 0644]
Documentation/git-format-patch.txt [new file with mode: 0644]
Documentation/git-fsck-objects.txt [new file with mode: 0644]
Documentation/git-get-tar-commit-id.txt [new file with mode: 0644]
Documentation/git-grep.txt [new file with mode: 0644]
Documentation/git-hash-object.txt [new file with mode: 0644]
Documentation/git-http-fetch.txt [new file with mode: 0644]
Documentation/git-http-push.txt [new file with mode: 0644]
Documentation/git-imap-send.txt [new file with mode: 0644]
Documentation/git-index-pack.txt [new file with mode: 0644]
Documentation/git-init-db.txt [new file with mode: 0644]
Documentation/git-local-fetch.txt [new file with mode: 0644]
Documentation/git-log.txt [new file with mode: 0644]
Documentation/git-lost-found.txt [new file with mode: 0644]
Documentation/git-ls-files.txt [new file with mode: 0644]
Documentation/git-ls-remote.txt [new file with mode: 0644]
Documentation/git-ls-tree.txt [new file with mode: 0644]
Documentation/git-mailinfo.txt [new file with mode: 0644]
Documentation/git-mailsplit.txt [new file with mode: 0644]
Documentation/git-merge-base.txt [new file with mode: 0644]
Documentation/git-merge-index.txt [new file with mode: 0644]
Documentation/git-merge-one-file.txt [new file with mode: 0644]
Documentation/git-merge-tree.txt [new file with mode: 0644]
Documentation/git-merge.txt [new file with mode: 0644]
Documentation/git-mktag.txt [new file with mode: 0644]
Documentation/git-mktree.txt [new file with mode: 0644]
Documentation/git-mv.txt [new file with mode: 0644]
Documentation/git-name-rev.txt [new file with mode: 0644]
Documentation/git-p4import.txt [new file with mode: 0644]
Documentation/git-pack-objects.txt [new file with mode: 0644]
Documentation/git-pack-redundant.txt [new file with mode: 0644]
Documentation/git-parse-remote.txt [new file with mode: 0644]
Documentation/git-patch-id.txt [new file with mode: 0644]
Documentation/git-peek-remote.txt [new file with mode: 0644]
Documentation/git-prune-packed.txt [new file with mode: 0644]
Documentation/git-prune.txt [new file with mode: 0644]
Documentation/git-pull.txt [new file with mode: 0644]
Documentation/git-push.txt [new file with mode: 0644]
Documentation/git-quiltimport.txt [new file with mode: 0644]
Documentation/git-read-tree.txt [new file with mode: 0644]
Documentation/git-rebase.txt [new file with mode: 0644]
Documentation/git-receive-pack.txt [new file with mode: 0644]
Documentation/git-relink.txt [new file with mode: 0644]
Documentation/git-repack.txt [new file with mode: 0644]
Documentation/git-repo-config.txt [new file with mode: 0644]
Documentation/git-request-pull.txt [new file with mode: 0644]
Documentation/git-rerere.txt [new file with mode: 0644]
Documentation/git-reset.txt [new file with mode: 0644]
Documentation/git-resolve.txt [new file with mode: 0644]
Documentation/git-rev-list.txt [new file with mode: 0644]
Documentation/git-rev-parse.txt [new file with mode: 0644]
Documentation/git-revert.txt [new file with mode: 0644]
Documentation/git-rm.txt [new file with mode: 0644]
Documentation/git-send-email.txt [new file with mode: 0644]
Documentation/git-send-pack.txt [new file with mode: 0644]
Documentation/git-sh-setup.txt [new file with mode: 0644]
Documentation/git-shell.txt [new file with mode: 0644]
Documentation/git-shortlog.txt [new file with mode: 0644]
Documentation/git-show-branch.txt [new file with mode: 0644]
Documentation/git-show-index.txt [new file with mode: 0644]
Documentation/git-show.txt [new file with mode: 0644]
Documentation/git-ssh-fetch.txt [new file with mode: 0644]
Documentation/git-ssh-upload.txt [new file with mode: 0644]
Documentation/git-status.txt [new file with mode: 0644]
Documentation/git-stripspace.txt [new file with mode: 0644]
Documentation/git-svnimport.txt [new file with mode: 0644]
Documentation/git-symbolic-ref.txt [new file with mode: 0644]
Documentation/git-tag.txt [new file with mode: 0644]
Documentation/git-tar-tree.txt [new file with mode: 0644]
Documentation/git-tools.txt [new file with mode: 0644]
Documentation/git-unpack-file.txt [new file with mode: 0644]
Documentation/git-unpack-objects.txt [new file with mode: 0644]
Documentation/git-update-index.txt [new file with mode: 0644]
Documentation/git-update-ref.txt [new file with mode: 0644]
Documentation/git-update-server-info.txt [new file with mode: 0644]
Documentation/git-upload-pack.txt [new file with mode: 0644]
Documentation/git-var.txt [new file with mode: 0644]
Documentation/git-verify-pack.txt [new file with mode: 0644]
Documentation/git-verify-tag.txt [new file with mode: 0644]
Documentation/git-whatchanged.txt [new file with mode: 0644]
Documentation/git-write-tree.txt [new file with mode: 0644]
Documentation/git.txt [new file with mode: 0644]
Documentation/gitk.txt [new file with mode: 0644]
Documentation/glossary.txt [new file with mode: 0644]
Documentation/hooks.txt [new file with mode: 0644]
Documentation/howto-index.sh [new file with mode: 0755]
Documentation/howto/isolate-bugs-with-bisect.txt [new file with mode: 0644]
Documentation/howto/make-dist.txt [new file with mode: 0644]
Documentation/howto/rebase-and-edit.txt [new file with mode: 0644]
Documentation/howto/rebase-from-internal-branch.txt [new file with mode: 0644]
Documentation/howto/rebuild-from-update-hook.txt [new file with mode: 0644]
Documentation/howto/revert-branch-rebase.txt [new file with mode: 0644]
Documentation/howto/separating-topic-branches.txt [new file with mode: 0644]
Documentation/howto/update-hook-example.txt [new file with mode: 0644]
Documentation/howto/using-topic-branches.txt [new file with mode: 0644]
Documentation/install-webdoc.sh [new file with mode: 0755]
Documentation/merge-options.txt [new file with mode: 0644]
Documentation/merge-strategies.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt [new file with mode: 0644]
Documentation/repository-layout.txt [new file with mode: 0644]
Documentation/sort_glossary.pl [new file with mode: 0644]
Documentation/technical/pack-format.txt [new file with mode: 0644]
Documentation/technical/pack-heuristics.txt [new file with mode: 0644]
Documentation/technical/pack-protocol.txt [new file with mode: 0644]
Documentation/technical/trivial-merge.txt [new file with mode: 0644]
Documentation/tutorial-2.txt [new file with mode: 0644]
Documentation/tutorial.txt [new file with mode: 0644]
Documentation/urls.txt [new file with mode: 0644]
GIT-VERSION-GEN [new file with mode: 0755]
INSTALL [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
arm/sha1.c [new file with mode: 0644]
arm/sha1.h [new file with mode: 0644]
arm/sha1_arm.S [new file with mode: 0644]
base85.c [new file with mode: 0644]
blame.c [new file with mode: 0644]
blob.c [new file with mode: 0644]
blob.h [new file with mode: 0644]
builtin-add.c [new file with mode: 0644]
builtin-apply.c [new file with mode: 0644]
builtin-cat-file.c [new file with mode: 0644]
builtin-check-ref-format.c [new file with mode: 0644]
builtin-commit-tree.c [new file with mode: 0644]
builtin-count.c [new file with mode: 0644]
builtin-diff-files.c [new file with mode: 0644]
builtin-diff-index.c [new file with mode: 0644]
builtin-diff-stages.c [new file with mode: 0644]
builtin-diff-tree.c [new file with mode: 0644]
builtin-diff.c [new file with mode: 0644]
builtin-grep.c [new file with mode: 0644]
builtin-help.c [new file with mode: 0644]
builtin-init-db.c [new file with mode: 0644]
builtin-log.c [new file with mode: 0644]
builtin-ls-files.c [new file with mode: 0644]
builtin-ls-tree.c [new file with mode: 0644]
builtin-push.c [new file with mode: 0644]
builtin-read-tree.c [new file with mode: 0644]
builtin-rev-list.c [new file with mode: 0644]
builtin-rev-parse.c [new file with mode: 0644]
builtin-rm.c [new file with mode: 0644]
builtin-show-branch.c [new file with mode: 0644]
builtin-tar-tree.c [new file with mode: 0644]
builtin-upload-tar.c [new file with mode: 0644]
builtin.h [new file with mode: 0644]
cache-tree.c [new file with mode: 0644]
cache-tree.h [new file with mode: 0644]
cache.h [new file with mode: 0644]
checkout-index.c [new file with mode: 0644]
clone-pack.c [new file with mode: 0644]
combine-diff.c [new file with mode: 0644]
commit.c [new file with mode: 0644]
commit.h [new file with mode: 0644]
compat/inet_ntop.c [new file with mode: 0644]
compat/mmap.c [new file with mode: 0644]
compat/setenv.c [new file with mode: 0644]
compat/strcasestr.c [new file with mode: 0644]
compat/subprocess.py [new file with mode: 0644]
compat/unsetenv.c [new file with mode: 0644]
config.c [new file with mode: 0644]
connect.c [new file with mode: 0644]
contrib/README [new file with mode: 0644]
contrib/colordiff/README [new file with mode: 0644]
contrib/colordiff/colordiff.perl [new file with mode: 0755]
contrib/emacs/.gitignore [new file with mode: 0644]
contrib/emacs/Makefile [new file with mode: 0644]
contrib/emacs/git.el [new file with mode: 0644]
contrib/emacs/vc-git.el [new file with mode: 0644]
contrib/git-svn/.gitignore [new file with mode: 0644]
contrib/git-svn/Makefile [new file with mode: 0644]
contrib/git-svn/git-svn.perl [new file with mode: 0755]
contrib/git-svn/git-svn.txt [new file with mode: 0644]
contrib/git-svn/t/lib-git-svn.sh [new file with mode: 0644]
contrib/git-svn/t/t0000-contrib-git-svn.sh [new file with mode: 0644]
contrib/git-svn/t/t0001-contrib-git-svn-props.sh [new file with mode: 0644]
contrib/gitview/gitview [new file with mode: 0755]
contrib/gitview/gitview.txt [new file with mode: 0644]
contrib/remotes2config.sh [new file with mode: 0644]
convert-objects.c [new file with mode: 0644]
copy.c [new file with mode: 0644]
csum-file.c [new file with mode: 0644]
csum-file.h [new file with mode: 0644]
ctype.c [new file with mode: 0644]
daemon.c [new file with mode: 0644]
date.c [new file with mode: 0644]
delta.h [new file with mode: 0644]
describe.c [new file with mode: 0644]
diff-delta.c [new file with mode: 0644]
diff-lib.c [new file with mode: 0644]
diff.c [new file with mode: 0644]
diff.h [new file with mode: 0644]
diffcore-break.c [new file with mode: 0644]
diffcore-delta.c [new file with mode: 0644]
diffcore-order.c [new file with mode: 0644]
diffcore-pickaxe.c [new file with mode: 0644]
diffcore-rename.c [new file with mode: 0644]
diffcore.h [new file with mode: 0644]
dir.c [new file with mode: 0644]
dir.h [new file with mode: 0644]
dump-cache-tree.c [new file with mode: 0644]
entry.c [new file with mode: 0644]
environment.c [new file with mode: 0644]
exec_cmd.c [new file with mode: 0644]
exec_cmd.h [new file with mode: 0644]
fetch-clone.c [new file with mode: 0644]
fetch-pack.c [new file with mode: 0644]
fetch.c [new file with mode: 0644]
fetch.h [new file with mode: 0644]
fsck-objects.c [new file with mode: 0644]
generate-cmdlist.sh [new file with mode: 0755]
get-tar-commit-id.c [new file with mode: 0644]
git-am.sh [new file with mode: 0755]
git-annotate.perl [new file with mode: 0755]
git-applymbox.sh [new file with mode: 0755]
git-applypatch.sh [new file with mode: 0755]
git-archimport.perl [new file with mode: 0755]
git-bisect.sh [new file with mode: 0755]
git-branch.sh [new file with mode: 0755]
git-checkout.sh [new file with mode: 0755]
git-cherry.sh [new file with mode: 0755]
git-clean.sh [new file with mode: 0755]
git-clone.sh [new file with mode: 0755]
git-commit.sh [new file with mode: 0755]
git-compat-util.h [new file with mode: 0644]
git-cvsexportcommit.perl [new file with mode: 0755]
git-cvsimport.perl [new file with mode: 0755]
git-cvsserver.perl [new file with mode: 0755]
git-fetch.sh [new file with mode: 0755]
git-fmt-merge-msg.perl [new file with mode: 0755]
git-lost-found.sh [new file with mode: 0755]
git-ls-remote.sh [new file with mode: 0755]
git-merge-octopus.sh [new file with mode: 0755]
git-merge-one-file.sh [new file with mode: 0755]
git-merge-ours.sh [new file with mode: 0755]
git-merge-recursive.py [new file with mode: 0755]
git-merge-resolve.sh [new file with mode: 0755]
git-merge-stupid.sh [new file with mode: 0755]
git-merge.sh [new file with mode: 0755]
git-mv.perl [new file with mode: 0755]
git-p4import.py [new file with mode: 0644]
git-parse-remote.sh [new file with mode: 0755]
git-prune.sh [new file with mode: 0755]
git-pull.sh [new file with mode: 0755]
git-push.sh [new file with mode: 0755]
git-quiltimport.sh [new file with mode: 0755]
git-rebase.sh [new file with mode: 0755]
git-relink.perl [new file with mode: 0755]
git-repack.sh [new file with mode: 0755]
git-request-pull.sh [new file with mode: 0755]
git-rerere.perl [new file with mode: 0755]
git-reset.sh [new file with mode: 0755]
git-resolve.sh [new file with mode: 0755]
git-revert.sh [new file with mode: 0755]
git-send-email.perl [new file with mode: 0755]
git-sh-setup.sh [new file with mode: 0755]
git-shortlog.perl [new file with mode: 0755]
git-svnimport.perl [new file with mode: 0755]
git-tag.sh [new file with mode: 0755]
git-verify-tag.sh [new file with mode: 0755]
git.c [new file with mode: 0644]
git.spec.in [new file with mode: 0644]
gitMergeCommon.py [new file with mode: 0644]
hash-object.c [new file with mode: 0644]
http-fetch.c [new file with mode: 0644]
http-push.c [new file with mode: 0644]
http.c [new file with mode: 0644]
http.h [new file with mode: 0644]
ident.c [new file with mode: 0644]
imap-send.c [new file with mode: 0644]
index-pack.c [new file with mode: 0644]
local-fetch.c [new file with mode: 0644]
lockfile.c [new file with mode: 0644]
log-tree.c [new file with mode: 0644]
log-tree.h [new file with mode: 0644]
mailinfo.c [new file with mode: 0644]
mailsplit.c [new file with mode: 0644]
merge-base.c [new file with mode: 0644]
merge-index.c [new file with mode: 0644]
merge-tree.c [new file with mode: 0644]
mktag.c [new file with mode: 0644]
mktree.c [new file with mode: 0644]
mozilla-sha1/sha1.c [new file with mode: 0644]
mozilla-sha1/sha1.h [new file with mode: 0644]
name-rev.c [new file with mode: 0644]
object.c [new file with mode: 0644]
object.h [new file with mode: 0644]
pack-check.c [new file with mode: 0644]
pack-objects.c [new file with mode: 0644]
pack-redundant.c [new file with mode: 0644]
pack.h [new file with mode: 0644]
pager.c [new file with mode: 0644]
patch-delta.c [new file with mode: 0644]
patch-id.c [new file with mode: 0644]
path.c [new file with mode: 0644]
peek-remote.c [new file with mode: 0644]
pkt-line.c [new file with mode: 0644]
pkt-line.h [new file with mode: 0644]
ppc/sha1.c [new file with mode: 0644]
ppc/sha1.h [new file with mode: 0644]
ppc/sha1ppc.S [new file with mode: 0644]
prune-packed.c [new file with mode: 0644]
quote.c [new file with mode: 0644]
quote.h [new file with mode: 0644]
read-cache.c [new file with mode: 0644]
receive-pack.c [new file with mode: 0644]
refs.c [new file with mode: 0644]
refs.h [new file with mode: 0644]
repo-config.c [new file with mode: 0644]
revision.c [new file with mode: 0644]
revision.h [new file with mode: 0644]
rsh.c [new file with mode: 0644]
rsh.h [new file with mode: 0644]
run-command.c [new file with mode: 0644]
run-command.h [new file with mode: 0644]
send-pack.c [new file with mode: 0644]
server-info.c [new file with mode: 0644]
setup.c [new file with mode: 0644]
sha1_file.c [new file with mode: 0644]
sha1_name.c [new file with mode: 0644]
shell.c [new file with mode: 0644]
show-index.c [new file with mode: 0644]
ssh-fetch.c [new file with mode: 0644]
ssh-pull.c [new file with mode: 0644]
ssh-push.c [new file with mode: 0644]
ssh-upload.c [new file with mode: 0644]
strbuf.c [new file with mode: 0644]
strbuf.h [new file with mode: 0644]
stripspace.c [new file with mode: 0644]
symbolic-ref.c [new file with mode: 0644]
t/.gitignore [new file with mode: 0644]
t/Makefile [new file with mode: 0644]
t/README [new file with mode: 0644]
t/annotate-tests.sh [new file with mode: 0644]
t/diff-lib.sh [new file with mode: 0755]
t/lib-read-tree-m-3way.sh [new file with mode: 0755]
t/t0000-basic.sh [new file with mode: 0755]
t/t0010-racy-git.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh [new file with mode: 0755]
t/t1001-read-tree-m-2way.sh [new file with mode: 0755]
t/t1002-read-tree-m-u-2way.sh [new file with mode: 0755]
t/t1100-commit-tree-options.sh [new file with mode: 0755]
t/t1200-tutorial.sh [new file with mode: 0755]
t/t1300-repo-config.sh [new file with mode: 0755]
t/t1400-update-ref.sh [new file with mode: 0755]
t/t2000-checkout-cache-clash.sh [new file with mode: 0755]
t/t2001-checkout-cache-clash.sh [new file with mode: 0755]
t/t2002-checkout-cache-u.sh [new file with mode: 0755]
t/t2003-checkout-cache-mkdir.sh [new file with mode: 0755]
t/t2004-checkout-cache-temp.sh [new file with mode: 0755]
t/t2100-update-cache-badpath.sh [new file with mode: 0755]
t/t2101-update-index-reupdate.sh [new file with mode: 0755]
t/t3000-ls-files-others.sh [new file with mode: 0755]
t/t3001-ls-files-others-exclude.sh [new file with mode: 0755]
t/t3002-ls-files-dashpath.sh [new file with mode: 0755]
t/t3010-ls-files-killed-modified.sh [new file with mode: 0755]
t/t3020-ls-files-error-unmatch.sh [new file with mode: 0755]
t/t3100-ls-tree-restrict.sh [new file with mode: 0755]
t/t3101-ls-tree-dirname.sh [new file with mode: 0755]
t/t3200-branch.sh [new file with mode: 0755]
t/t3300-funny-names.sh [new file with mode: 0755]
t/t3400-rebase.sh [new file with mode: 0755]
t/t3401-rebase-partial.sh [new file with mode: 0755]
t/t3500-cherry.sh [new file with mode: 0755]
t/t3600-rm.sh [new file with mode: 0755]
t/t3700-add.sh [new file with mode: 0755]
t/t4000-diff-format.sh [new file with mode: 0755]
t/t4001-diff-rename.sh [new file with mode: 0755]
t/t4002-diff-basic.sh [new file with mode: 0755]
t/t4003-diff-rename-1.sh [new file with mode: 0755]
t/t4004-diff-rename-symlink.sh [new file with mode: 0755]
t/t4005-diff-rename-2.sh [new file with mode: 0755]
t/t4006-diff-mode.sh [new file with mode: 0755]
t/t4007-rename-3.sh [new file with mode: 0755]
t/t4008-diff-break-rewrite.sh [new file with mode: 0755]
t/t4009-diff-rename-4.sh [new file with mode: 0755]
t/t4010-diff-pathspec.sh [new file with mode: 0755]
t/t4011-diff-symlink.sh [new file with mode: 0755]
t/t4012-diff-binary.sh [new file with mode: 0755]
t/t4100-apply-stat.sh [new file with mode: 0755]
t/t4100/t-apply-1.expect [new file with mode: 0644]
t/t4100/t-apply-1.patch [new file with mode: 0644]
t/t4100/t-apply-2.expect [new file with mode: 0644]
t/t4100/t-apply-2.patch [new file with mode: 0644]
t/t4100/t-apply-3.expect [new file with mode: 0644]
t/t4100/t-apply-3.patch [new file with mode: 0644]
t/t4100/t-apply-4.expect [new file with mode: 0644]
t/t4100/t-apply-4.patch [new file with mode: 0644]
t/t4100/t-apply-5.expect [new file with mode: 0644]
t/t4100/t-apply-5.patch [new file with mode: 0644]
t/t4100/t-apply-6.expect [new file with mode: 0644]
t/t4100/t-apply-6.patch [new file with mode: 0644]
t/t4100/t-apply-7.expect [new file with mode: 0644]
t/t4100/t-apply-7.patch [new file with mode: 0644]
t/t4101-apply-nonl.sh [new file with mode: 0755]
t/t4102-apply-rename.sh [new file with mode: 0755]
t/t4103-apply-binary.sh [new file with mode: 0755]
t/t4109-apply-multifrag.sh [new file with mode: 0755]
t/t4110-apply-scan.sh [new file with mode: 0755]
t/t4112-apply-renames.sh [new file with mode: 0755]
t/t4113-apply-ending.sh [new file with mode: 0755]
t/t5000-tar-tree.sh [new file with mode: 0755]
t/t5300-pack-object.sh [new file with mode: 0755]
t/t5400-send-pack.sh [new file with mode: 0755]
t/t5500-fetch-pack.sh [new file with mode: 0755]
t/t5600-clone-fail-cleanup.sh [new file with mode: 0755]
t/t5700-clone-reference.sh [new file with mode: 0755]
t/t5710-info-alternate.sh [new file with mode: 0755]
t/t6000lib.sh [new file with mode: 0755]
t/t6002-rev-list-bisect.sh [new file with mode: 0755]
t/t6003-rev-list-topo-order.sh [new file with mode: 0755]
t/t6010-merge-base.sh [new file with mode: 0755]
t/t6020-merge-df.sh [new file with mode: 0755]
t/t6021-merge-criss-cross.sh [new file with mode: 0755]
t/t6022-merge-rename.sh [new file with mode: 0755]
t/t6101-rev-parse-parents.sh [new file with mode: 0755]
t/t7001-mv.sh [new file with mode: 0755]
t/t7101-reset.sh [new file with mode: 0755]
t/t8001-annotate.sh [new file with mode: 0755]
t/t8002-blame.sh [new file with mode: 0755]
t/t9001-send-email.sh [new file with mode: 0755]
t/test-lib.sh [new file with mode: 0755]
t/test4012.png [new file with mode: 0644]
tag.c [new file with mode: 0644]
tag.h [new file with mode: 0644]
tar.h [new file with mode: 0644]
templates/.gitignore [new file with mode: 0644]
templates/Makefile [new file with mode: 0644]
templates/branches-- [new file with mode: 0644]
templates/hooks--applypatch-msg [new file with mode: 0644]
templates/hooks--commit-msg [new file with mode: 0644]
templates/hooks--post-commit [new file with mode: 0644]
templates/hooks--post-update [new file with mode: 0644]
templates/hooks--pre-applypatch [new file with mode: 0644]
templates/hooks--pre-commit [new file with mode: 0644]
templates/hooks--pre-rebase [new file with mode: 0644]
templates/hooks--update [new file with mode: 0644]
templates/info--exclude [new file with mode: 0644]
templates/remotes-- [new file with mode: 0644]
templates/this--description [new file with mode: 0644]
test-date.c [new file with mode: 0644]
test-delta.c [new file with mode: 0644]
tree-diff.c [new file with mode: 0644]
tree-walk.c [new file with mode: 0644]
tree-walk.h [new file with mode: 0644]
tree.c [new file with mode: 0644]
tree.h [new file with mode: 0644]
unpack-file.c [new file with mode: 0644]
unpack-objects.c [new file with mode: 0644]
update-index.c [new file with mode: 0644]
update-ref.c [new file with mode: 0644]
update-server-info.c [new file with mode: 0644]
upload-pack.c [new file with mode: 0644]
usage.c [new file with mode: 0644]
var.c [new file with mode: 0644]
verify-pack.c [new file with mode: 0644]
write-tree.c [new file with mode: 0644]
xdiff-interface.c [new file with mode: 0644]
xdiff-interface.h [new file with mode: 0644]
xdiff/xdiff.h [new file with mode: 0644]
xdiff/xdiffi.c [new file with mode: 0644]
xdiff/xdiffi.h [new file with mode: 0644]
xdiff/xemit.c [new file with mode: 0644]
xdiff/xemit.h [new file with mode: 0644]
xdiff/xinclude.h [new file with mode: 0644]
xdiff/xmacros.h [new file with mode: 0644]
xdiff/xprepare.c [new file with mode: 0644]
xdiff/xprepare.h [new file with mode: 0644]
xdiff/xtypes.h [new file with mode: 0644]
xdiff/xutils.c [new file with mode: 0644]
xdiff/xutils.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..afd0876
--- /dev/null
@@ -0,0 +1,138 @@
+GIT-VERSION-FILE
+git
+git-add
+git-am
+git-annotate
+git-apply
+git-applymbox
+git-applypatch
+git-archimport
+git-bisect
+git-branch
+git-cat-file
+git-check-ref-format
+git-checkout
+git-checkout-index
+git-cherry
+git-cherry-pick
+git-clean
+git-clone
+git-clone-pack
+git-commit
+git-commit-tree
+git-convert-objects
+git-count-objects
+git-cvsexportcommit
+git-cvsimport
+git-cvsserver
+git-daemon
+git-diff
+git-diff-files
+git-diff-index
+git-diff-stages
+git-diff-tree
+git-describe
+git-fetch
+git-fetch-pack
+git-findtags
+git-fmt-merge-msg
+git-format-patch
+git-fsck-objects
+git-get-tar-commit-id
+git-grep
+git-hash-object
+git-http-fetch
+git-http-push
+git-imap-send
+git-index-pack
+git-init-db
+git-local-fetch
+git-log
+git-lost-found
+git-ls-files
+git-ls-remote
+git-ls-tree
+git-mailinfo
+git-mailsplit
+git-merge
+git-merge-base
+git-merge-index
+git-merge-tree
+git-merge-octopus
+git-merge-one-file
+git-merge-ours
+git-merge-recursive
+git-merge-resolve
+git-merge-stupid
+git-mktag
+git-mktree
+git-name-rev
+git-mv
+git-pack-redundant
+git-pack-objects
+git-parse-remote
+git-patch-id
+git-peek-remote
+git-prune
+git-prune-packed
+git-pull
+git-push
+git-quiltimport
+git-read-tree
+git-rebase
+git-receive-pack
+git-relink
+git-repack
+git-repo-config
+git-request-pull
+git-rerere
+git-reset
+git-resolve
+git-rev-list
+git-rev-parse
+git-revert
+git-rm
+git-send-email
+git-send-pack
+git-sh-setup
+git-shell
+git-shortlog
+git-show
+git-show-branch
+git-show-index
+git-ssh-fetch
+git-ssh-pull
+git-ssh-push
+git-ssh-upload
+git-status
+git-stripspace
+git-svnimport
+git-symbolic-ref
+git-tag
+git-tar-tree
+git-unpack-file
+git-unpack-objects
+git-update-index
+git-update-ref
+git-update-server-info
+git-upload-pack
+git-upload-tar
+git-var
+git-verify-pack
+git-verify-tag
+git-whatchanged
+git-write-tree
+git-core-*/?*
+test-date
+test-delta
+test-dump-cache-tree
+common-cmds.h
+*.tar.gz
+*.dsc
+*.deb
+git-core.spec
+*.exe
+*.[ao]
+*.py[co]
+config.mak
+git-blame
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..6ff87c4
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,361 @@
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ HOWEVER, in order to allow a migration to GPLv3 if that seems like
+ a good idea, I also ask that people involved with the project make
+ their preferences known. In particular, if you trust me to make that
+ decision, you might note so in your copyright message, ie something
+ like
+
+       This file is licensed under the GPL v2, or a later version
+       at the discretion of Linus.
+
+  might avoid issues. But we can also just decide to synchronize and
+  contact all copyright holders on record if/when the occasion arises.
+
+                       Linus Torvalds
+
+----------------------------------------
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Documentation/.gitignore b/Documentation/.gitignore
new file mode 100644 (file)
index 0000000..c87c61a
--- /dev/null
@@ -0,0 +1,7 @@
+*.xml
+*.html
+*.1
+*.7
+howto-index.txt
+doc.dep
+README
diff --git a/Documentation/Makefile b/Documentation/Makefile
new file mode 100644 (file)
index 0000000..2b0efe7
--- /dev/null
@@ -0,0 +1,111 @@
+MAN1_TXT= \
+       $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
+               $(wildcard git-*.txt)) \
+       gitk.txt
+MAN7_TXT=git.txt
+
+DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
+
+ARTICLES = tutorial
+ARTICLES += tutorial-2
+ARTICLES += core-tutorial
+ARTICLES += cvs-migration
+ARTICLES += diffcore
+ARTICLES += howto-index
+ARTICLES += repository-layout
+ARTICLES += hooks
+ARTICLES += everyday
+ARTICLES += git-tools
+# with their own formatting rules.
+SP_ARTICLES = glossary howto/revert-branch-rebase
+
+DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
+
+DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
+
+prefix?=$(HOME)
+bin=$(prefix)/bin
+mandir=$(prefix)/man
+man1=$(mandir)/man1
+man7=$(mandir)/man7
+# DESTDIR=
+
+INSTALL?=install
+
+#
+# Please note that there is a minor bug in asciidoc.
+# The version after 6.0.3 _will_ include the patch found here:
+#   http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2
+#
+# Until that version is released you may have to apply the patch
+# yourself - yes, all 6 characters of it!
+#
+
+all: html man
+
+html: $(DOC_HTML)
+
+
+man: man1 man7
+man1: $(DOC_MAN1)
+man7: $(DOC_MAN7)
+
+install: man
+       $(INSTALL) -d -m755 $(DESTDIR)$(man1) $(DESTDIR)$(man7)
+       $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1)
+       $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7)
+
+
+#
+# Determine "include::" file references in asciidoc files.
+#
+doc.dep : $(wildcard *.txt) build-docdep.perl
+       rm -f $@+ $@
+       perl ./build-docdep.perl >$@+
+       mv $@+ $@
+
+-include doc.dep
+
+git.7: README
+
+README: ../README
+       cp $< $@
+
+
+clean:
+       rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep README
+
+%.html : %.txt
+       asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
+
+%.1 %.7 : %.xml
+       xmlto -m callouts.xsl man $<
+
+%.xml : %.txt
+       asciidoc -b docbook -d manpage -f asciidoc.conf $<
+
+git.html: git.txt README
+
+glossary.html : glossary.txt sort_glossary.pl
+       cat $< | \
+       perl sort_glossary.pl | \
+       asciidoc -b xhtml11 - > glossary.html
+
+howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
+       rm -f $@+ $@
+       sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
+       mv $@+ $@
+
+$(patsubst %,%.html,$(ARTICLES)) : %.html : %.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 - >$@+
+       mv $@+ $@
+
+install-webdoc : html
+       sh ./install-webdoc.sh $(WEBDOC_DEST)
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
new file mode 100644 (file)
index 0000000..8601949
--- /dev/null
@@ -0,0 +1,306 @@
+I started reading over the SubmittingPatches document for Linux
+kernel, primarily because I wanted to have a document similar to
+it for the core GIT to make sure people understand what they are
+doing when they write "Signed-off-by" line.
+
+But the patch submission requirements are a lot more relaxed
+here on the technical/contents front, because the core GIT is
+thousand times smaller ;-).  So here is only the relevant bits.
+
+
+(1) Make separate commits for logically separate changes.
+
+Unless your patch is really trivial, you should not be sending
+out a patch that was generated between your working tree and
+your commit head.  Instead, always make a commit with complete
+commit message and generate a series of patches from your
+repository.  It is a good discipline.
+
+Describe the technical detail of the change(s).
+
+If your description starts to get too long, that's a sign that you
+probably need to split up your commit to finer grained pieces.
+
+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.
+
+
+(2) Generate your patch using git tools out of your commits.
+
+git based diff tools (git, Cogito, and StGIT included) generate
+unidiff which is the preferred format.
+
+You do not have to be afraid to use -M option to "git diff" or
+"git format-patch", if your patch involves file renames.  The
+receiving end can handle them just fine.
+
+Please make sure your patch does not include any extra files
+which do not belong in a patch submission.  Make sure to review
+your patch after generating it, to ensure accuracy.  Before
+sending out, please make sure it cleanly applies to the "master"
+branch head.  If you are preparing a work based on "next" branch,
+that is fine, but please mark it as such.
+
+
+(3) Sending your patches.
+
+People on the git mailing list need to be able to read and
+comment on the changes you are submitting.  It is important for
+a developer to be able to "quote" your changes, using standard
+e-mail tools, so that they may comment on specific portions of
+your code.  For this reason, all patches should be submited
+"inline".  WARNING: Be wary of your MUAs word-wrap
+corrupting your patch.  Do not cut-n-paste your patch; you can
+lose tabs that way if you are not careful.
+
+It is a common convention to prefix your subject line with
+[PATCH].  This lets people easily distinguish patches from other
+e-mail discussions.
+
+"git format-patch" command follows the best current practice to
+format the body of an e-mail message.  At the beginning of the
+patch should come your commit message, ending with the
+Signed-off-by: lines, and a line that consists of three dashes,
+followed by the diffstat information and the patch itself.  If
+you are forwarding a patch from somebody else, optionally, at
+the beginning of the e-mail message just before the commit
+message starts, you can put a "From: " line to name that person.
+
+You often want to add additional explanation about the patch,
+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
+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
+process.  This does not decrease the likelihood of your
+MIME-attached change being accepted, but it makes it more likely
+that it will be postponed.
+
+Exception:  If your mailer is mangling patches then someone may ask
+you to re-send them using MIME, that is OK.
+
+Do not PGP sign your patch, at least for now.  Most likely, your
+maintainer or other people on the list would not have your PGP
+key and would not bother obtaining it anyway.  Your patch is not
+judged by who you are; a good patch from an unknown origin has a
+far better chance of being accepted than a patch from a known,
+respected origin that is done poorly or does incorrect things.
+
+If you really really really really want to do a PGP signed
+patch, format it as "multipart/signed", not a text/plain message
+that starts with '-----BEGIN PGP SIGNED MESSAGE-----'.  That is
+not a text/plain, it's something else.
+
+Note that your maintainer does not necessarily read everything
+on the git mailing list.  If your patch is for discussion first,
+send it "To:" the mailing list, and optionally "cc:" him.  If it
+is trivially correct or after the list reached a consensus, send
+it "To:" the maintainer and optionally "cc:" the list.
+
+
+(6) Sign your work
+
+To improve tracking of who did what, we've borrowed the
+"sign-off" procedure from the Linux kernel project on patches
+that are being emailed around.  Although core GIT is a lot
+smaller project it is a good discipline to follow it.
+
+The sign-off is a simple line at the end of the explanation for
+the patch, which certifies that you wrote it or otherwise have
+the right to pass it on as a open-source patch.  The rules are
+pretty simple: if you can certify the below:
+
+        Developer's Certificate of Origin 1.1
+
+        By making a contribution to this project, I certify that:
+
+        (a) The contribution was created in whole or in part by me and I
+            have the right to submit it under the open source license
+            indicated in the file; or
+
+        (b) The contribution is based upon previous work that, to the best
+            of my knowledge, is covered under an appropriate open source
+            license and I have the right under that license to submit that
+            work with modifications, whether created in whole or in part
+            by me, under the same open source license (unless I am
+            permitted to submit under a different license), as indicated
+            in the file; or
+
+        (c) The contribution was provided directly to me by some other
+            person who certified (a), (b) or (c) and I have not modified
+            it.
+
+       (d) I understand and agree that this project and the contribution
+           are public and that a record of the contribution (including all
+           personal information I submit with it, including my sign-off) is
+           maintained indefinitely and may be redistributed consistent with
+           this project or the open source license(s) involved.
+
+then you just add a line saying
+
+       Signed-off-by: Random J Developer <random@developer.example.org>
+
+Some people also put extra tags at the end.  They'll just be ignored for
+now, but you can do this to mark internal company procedures or just
+point out some special detail about the sign-off.
+
+
+------------------------------------------------
+MUA specific hints
+
+Some of patches I receive or pick up from the list share common
+patterns of breakage.  Please make sure your MUA is set up
+properly not to corrupt whitespaces.  Here are two common ones
+I have seen:
+
+* Empty context lines that do not have _any_ whitespace.
+
+* Non empty context lines that have one extra whitespace at the
+  beginning.
+
+One test you could do yourself if your MUA is set up correctly is:
+
+* Send the patch to yourself, exactly the way you would, except
+  To: and Cc: lines, which would not contain the list and
+  maintainer address.
+
+* Save that patch to a file in UNIX mailbox format.  Call it say
+  a.patch.
+
+* Try to apply to the tip of the "master" branch from the
+  git.git public repository:
+
+    $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply
+    $ git checkout test-apply
+    $ git reset --hard
+    $ git applymbox a.patch
+
+If it does not apply correctly, there can be various reasons.
+
+* Your patch itself does not apply cleanly.  That is _bad_ but
+  does not have much to do with your MUA.  Please rebase the
+  patch appropriately.
+
+* Your MUA corrupted your patch; applymbox would complain that
+  the patch does not apply.  Look at .dotest/ subdirectory and
+  see what 'patch' file contains and check for the common
+  corruption patterns mentioned above.
+
+* While you are at it, check what are in 'info' and
+  'final-commit' files as well.  If what is in 'final-commit' is
+  not exactly what you would want to see in the commit log
+  message, it is very likely that your maintainer would end up
+  hand editing the log message when he applies your patch.
+  Things like "Hi, this is my first patch.\n", if you really
+  want to put in the patch e-mail, should come after the
+  three-dash line that signals the end of the commit message.
+
+
+Pine
+----
+
+(Johannes Schindelin)
+
+I don't know how many people still use pine, but for those poor
+souls it may be good to mention that the quell-flowed-text is
+needed for recent versions.
+
+... the "no-strip-whitespace-before-send" option, too. AFAIK it
+was introduced in 4.60.
+
+(Linus Torvalds)
+
+And 4.58 needs at least this.
+
+---
+diff-tree 8326dd8350be64ac7fc805f6563a1d61ad10d32c (from e886a61f76edf5410573e92e38ce22974f9c40f1)
+Author: Linus Torvalds <torvalds@g5.osdl.org>
+Date:   Mon Aug 15 17:23:51 2005 -0700
+
+    Fix pine whitespace-corruption bug
+
+    There's no excuse for unconditionally removing whitespace from
+    the pico buffers on close.
+
+diff --git a/pico/pico.c b/pico/pico.c
+--- a/pico/pico.c
++++ b/pico/pico.c
+@@ -219,7 +219,9 @@ PICO *pm;
+           switch(pico_all_done){      /* prepare for/handle final events */
+             case COMP_EXIT :          /* already confirmed */
+               packheader();
++#if 0
+               stripwhitespace();
++#endif
+               c |= COMP_EXIT;
+               break;
+
+(Daniel Barkalow)
+
+> A patch to SubmittingPatches, MUA specific help section for
+> users of Pine 4.63 would be very much appreciated.
+
+Ah, it looks like a recent version changed the default behavior to do the
+right thing, and inverted the sense of the configuration option. (Either
+that or Gentoo did it.) So you need to set the
+"no-strip-whitespace-before-send" option, unless the option you have is
+"strip-whitespace-before-send", in which case you should avoid checking
+it.
+
+
+Thunderbird
+-----------
+
+(A Large Angry SCM)
+
+Here are some hints on how to successfully submit patches inline using
+Thunderbird.
+
+This recipe appears to work with the current [*1*] Thunderbird from Suse.
+
+The following Thunderbird extensions are needed:
+       AboutConfig 0.5
+               http://aboutconfig.mozdev.org/
+       External Editor 0.7.2
+               http://globs.org/articles.php?lng=en&pg=8
+
+1) Prepare the patch as a text file using your method of choice.
+
+2) Before opening a compose window, use Edit->Account Settings to
+uncheck the "Compose messages in HTML format" setting in the
+"Composition & Addressing" panel of the account to be used to send the
+patch. [*2*]
+
+3) In the main Thunderbird window, _before_ you open the compose window
+for the patch, use Tools->about:config to set the following to the
+indicated values:
+       mailnews.send_plaintext_flowed  => false
+       mailnews.wraplength             => 0
+
+4) Open a compose window and click the external editor icon.
+
+5) In the external editor window, read in the patch file and exit the
+editor normally.
+
+6) Back in the compose window: Add whatever other text you wish to the
+message, complete the addressing and subject fields, and press send.
+
+7) Optionally, undo the about:config/account settings changes made in
+steps 2 & 3.
+
+
+[Footnotes]
+*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse
+9.3 professional updates.
+
+*2* It may be possible to do this with about:config and the following
+settings but I haven't tried, yet.
+       mail.html_compose                       => false
+       mail.identity.default.compose_html      => false
+       mail.identity.id?.compose_html          => false
+
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
new file mode 100644 (file)
index 0000000..7ce7151
--- /dev/null
@@ -0,0 +1,36 @@
+## gitlink: macro
+#
+# Usage: gitlink:command[manpage-section]
+#
+# Note, {0} is the manpage section, while {target} is the command.
+#
+# Show GIT link as: <command>(<section>); if section is defined, else just show
+# the command.
+
+[attributes]
+caret=^
+
+ifdef::backend-docbook[]
+[gitlink-inlinemacro]
+{0%{target}}
+{0#<citerefentry>}
+{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
+{0#</citerefentry>}
+endif::backend-docbook[]
+
+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[]
+
+ifdef::backend-xhtml11[]
+[gitlink-inlinemacro]
+<a href="{target}.html">{target}{0?({0})}</a>
+endif::backend-xhtml11[]
+
+
diff --git a/Documentation/build-docdep.perl b/Documentation/build-docdep.perl
new file mode 100755 (executable)
index 0000000..489389c
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+my %include = ();
+my %included = ();
+
+for my $text (<*.txt>) {
+    open I, '<', $text || die "cannot read: $text";
+    while (<I>) {
+       if (/^include::/) {
+           chomp;
+           s/^include::\s*//;
+           s/\[\]//;
+           $include{$text}{$_} = 1;
+           $included{$_} = 1;
+       }
+    }
+    close I;
+}
+
+# Do we care about chained includes???
+my $changed = 1;
+while ($changed) {
+    $changed = 0;
+    while (my ($text, $included) = each %include) {
+       for my $i (keys %$included) {
+           # $text has include::$i; if $i includes $j
+           # $text indirectly includes $j.
+           if (exists $include{$i}) {
+               for my $j (keys %{$include{$i}}) {
+                   if (!exists $include{$text}{$j}) {
+                       $include{$text}{$j} = 1;
+                       $included{$j} = 1;
+                       $changed = 1;
+                   }
+               }
+           }
+       }
+    }
+}
+
+while (my ($text, $included) = each %include) {
+    if (! exists $included{$text} &&
+       (my $base = $text) =~ s/\.txt$//) {
+       my ($suffix) = '1';
+       if ($base eq 'git') {
+           $suffix = '7'; # yuck...
+       }
+       print "$base.html $base.$suffix : ", join(" ", keys %$included), "\n";
+    }
+}
diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl
new file mode 100644 (file)
index 0000000..ad03755
--- /dev/null
@@ -0,0 +1,16 @@
+<!-- callout.xsl: converts asciidoc callouts to man page format -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:template match="co">
+       <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+       <xsl:text>.sp&#10;</xsl:text>
+       <xsl:apply-templates/>
+       <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+       <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
+       <xsl:apply-templates/>
+       <xsl:text>.br&#10;</xsl:text>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/Documentation/config.txt b/Documentation/config.txt
new file mode 100644 (file)
index 0000000..c861c6c
--- /dev/null
@@ -0,0 +1,191 @@
+CONFIGURATION FILE
+------------------
+
+The git configuration file contains a number of variables that affect
+the git commands behavior. They can be used by both the git plumbing
+and the porcelains. The variables are divided to 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.
+
+The syntax is fairly flexible and permissive; whitespaces are mostly
+ignored. The '#' and ';' characters begin commends 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.
+
+Example
+~~~~~~~
+
+       # Core variables
+       [core]
+               ; Don't trust file modes
+               filemode = false
+
+       # Our diff algorithm
+       [diff]
+               external = "/usr/local/bin/gnu-diff -u"
+               renames = true
+
+Variables
+~~~~~~~~~
+
+Note that this list is non-comprehensive and not necessarily complete.
+For command-specific variables, you will find more detailed description
+in the appropriate manual page. You will find description of non-core
+porcelain configuration variables in the respective porcelain documentation.
+
+core.fileMode::
+       If false, the executable bit differences between the index and
+       the working copy are ignored; useful on broken filesystems like FAT.
+       See gitlink:git-update-index[1]. True by default.
+
+core.gitProxy::
+       A "proxy command" to execute (as 'command host port') instead
+       of establishing direct connection to the remote server when
+       using the git protocol for fetching. If the variable value is
+       in the "COMMAND for DOMAIN" format, the command is applied only
+       on hostnames ending with the specified domain string. This variable
+       may be set multiple times and is matched in the given order;
+       the first match wins.
+
+       Can be overridden by the 'GIT_PROXY_COMMAND' environment variable
+       (which always applies universally, without the special "for"
+       handling).
+
+core.ignoreStat::
+       The working copy files are assumed to stay unchanged until you
+       mark them otherwise manually - Git will not detect the file changes
+       by lstat() calls. This is useful on systems where those are very
+       slow, such as Microsoft Windows.  See gitlink:git-update-index[1].
+       False by default.
+
+core.preferSymlinkRefs::
+       Instead of the default "symref" format for HEAD
+       and other symbolic reference files, use symbolic links.
+       This is sometimes needed to work with old scripts that
+       expect HEAD to be a symbolic link.
+
+core.logAllRefUpdates::
+       If true, `git-update-ref` will append a line to
+       "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+       of the update.  If the file does not exist it will be
+       created automatically.  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 logging).
+
+core.repositoryFormatVersion::
+       Internal variable identifying the repository format and layout
+       version.
+
+core.sharedRepository::
+       If true, the repository is made shareable between several users
+       in a group (making sure all the files and objects are group-writable).
+       See gitlink:git-init-db[1]. False by default.
+
+core.warnAmbiguousRefs::
+       If true, git will warn you if the ref name you passed it is ambiguous
+       and might match multiple refs in the .git/refs/ tree. True by default.
+
+apply.whitespace::
+       Tells `git-apply` how to handle whitespaces, in the same way
+       as the '--whitespace' option. See gitlink:git-apply[1].
+
+diff.renameLimit::
+       The number of files to consider when performing the copy/rename
+       detection; equivalent to the git diff option '-l'.
+
+format.headers::
+       Additional email headers to include in a patch to be submitted
+       by mail.  See gitlink:git-format-patch[1].
+
+gitcvs.enabled::
+       Whether the cvs pserver interface is enabled for this repository.
+       See gitlink:git-cvsserver[1].
+
+gitcvs.logfile::
+       Path to a log file where the cvs pserver interface well... logs
+       various stuff. See gitlink:git-cvsserver[1].
+
+http.sslVerify::
+       Whether to verify the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_NO_VERIFY' environment
+       variable.
+
+http.sslCert::
+       File containing the SSL certificate when fetching or pushing
+       over HTTPS. Can be overriden by the 'GIT_SSL_CERT' environment
+       variable.
+
+http.sslKey::
+       File containing the SSL private key when fetching or pushing
+       over HTTPS. Can be overridden by the 'GIT_SSL_KEY' environment
+       variable.
+
+http.sslCAInfo::
+       File containing the certificates to verify the peer with when
+       fetching or pushing over HTTPS. Can be overridden by the
+       'GIT_SSL_CAINFO' environment variable.
+
+http.sslCAPath::
+       Path containing files with the CA certificates to verify the peer
+       with when fetching or pushing over HTTPS. Can be overriden
+       by the 'GIT_SSL_CAPATH' environment variable.
+
+http.maxRequests::
+       How many HTTP requests to launch in parallel. Can be overridden
+       by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
+
+http.lowSpeedLimit, http.lowSpeedTime::
+       If the HTTP transfer speed is less than 'http.lowSpeedLimit'
+       for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
+       Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
+       'GIT_HTTP_LOW_SPEED_TIME' environment variables.
+
+i18n.commitEncoding::
+       Character encoding the commit messages are stored in; git itself
+       does not care per se, but this information is necessary e.g. when
+       importing commits from emails or in the gitk graphical history
+       browser (and possibly at other places in the future or in other
+       porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
+
+merge.summary::
+       Whether to include summaries of merged commits in newly created
+       merge commit messages. False by default.
+
+pull.octopus::
+       The default merge strategy to use when pulling multiple branches
+       at once.
+
+pull.twohead::
+       The default merge strategy to use when pulling a single branch.
+
+show.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-show[1].
+
+showbranch.default::
+       The default set of branches for gitlink:git-show-branch[1].
+       See gitlink:git-show-branch[1].
+
+user.email::
+       Your email address to be recorded in any newly created commits.
+       Can be overridden by the 'GIT_AUTHOR_EMAIL' and 'GIT_COMMITTER_EMAIL'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+user.name::
+       Your full name to be recorded in any newly created commits.
+       Can be overridden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
+       environment variables.  See gitlink:git-commit-tree[1].
+
+whatchanged.difftree::
+       The default gitlink:git-diff-tree[1] arguments to be used
+       for gitlink:git-whatchanged[1].
+
+imap::
+       The configuration variables in the 'imap' section are described
+       in gitlink:git-imap-send[1].
diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt
new file mode 100644 (file)
index 0000000..1185897
--- /dev/null
@@ -0,0 +1,1722 @@
+A git core tutorial for developers
+==================================
+
+Introduction
+------------
+
+This is trying to be a short tutorial on setting up and using a git
+repository, mainly because being hands-on and using explicit examples is
+often the best way of explaining what is going on.
+
+In normal life, most people wouldn't use the "core" git programs
+directly, but rather script around them to make them more palatable. 
+Understanding the core git stuff may help some people get those scripts
+done, though, and it may also be instructive in helping people
+understand what it is that the higher-level helper scripts are actually
+doing. 
+
+The core git is often called "plumbing", with the prettier user
+interfaces on top of it called "porcelain". You may not want to use the
+plumbing directly very often, but it can be good to know what the
+plumbing does for when the porcelain isn't flushing.
+
+The material presented here often goes deep describing how things
+work internally.  If you are mostly interested in using git as a
+SCM, you can skip them during your first pass.
+
+[NOTE]
+And those "too deep" descriptions are often marked as Note.
+
+[NOTE]
+If you are already familiar with another version control system,
+like CVS, you may want to take a look at
+link:everyday.html[Everyday GIT in 20 commands or so] first
+before reading this.
+
+
+Creating a git repository
+-------------------------
+
+Creating a new git repository couldn't be easier: all git repositories start
+out empty, and the only thing you need to do is find yourself a
+subdirectory that you want to use as a working tree - either an empty
+one for a totally new project, or an existing working tree that you want
+to import into git. 
+
+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`:
+
+------------------------------------------------
+$ mkdir git-tutorial
+$ cd git-tutorial
+$ git-init-db
+------------------------------------------------
+
+to which git will reply
+
+----------------
+defaulting to local storage area
+----------------
+
+which is just git's way of saying that you haven't been doing anything
+strange, and that it will have created a local `.git` directory setup for
+your new project. You will now have a `.git` directory, and you can
+inspect that with `ls`. For your new empty project, it should show you
+three entries, among other things:
+
+ - a file called `HEAD`, that has `ref: refs/heads/master` in it.
+   This is similar to a symbolic link and points at
+   `refs/heads/master` relative to the `HEAD` file.
++
+Don't worry about the fact that the file that the `HEAD` link points to
+doesn't even exist yet -- you haven't created the commit that will
+start your `HEAD` development branch yet.
+
+ - a subdirectory called `objects`, which will contain all the
+   objects of your project. You should never have any real reason to
+   look at the objects directly, but you might want to know that these
+   objects are what contains all the real 'data' in your repository.
+
+ - a subdirectory called `refs`, which contains references to objects.
+
+In particular, the `refs` subdirectory will contain two other
+subdirectories, named `heads` and `tags` respectively. They do
+exactly what their names imply: they contain references to any number
+of different 'heads' of development (aka 'branches'), and to any
+'tags' that you have created to name specific versions in your
+repository.
+
+One note: the special `master` head is the default branch, which is
+why the `.git/HEAD` file was created points to it even if it
+doesn't yet exist. Basically, the `HEAD` link is supposed to always
+point to the branch you are working on right now, and you always
+start out expecting to work on the `master` branch.
+
+However, this is only a convention, and you can name your branches
+anything you want, and don't have to ever even 'have' a `master`
+branch. A number of the git tools will assume that `.git/HEAD` is
+valid, though.
+
+[NOTE]
+An 'object' is identified by its 160-bit SHA1 hash, aka 'object name',
+and a reference to an object is always the 40-byte hex
+representation of that SHA1 name. The files in the `refs`
+subdirectory are expected to contain these hex references
+(usually with a final `\'\n\'` at the end), and you should thus
+expect to see a number of 41-byte files containing these
+references in these `refs` subdirectories when you actually start
+populating your tree.
+
+[NOTE]
+An advanced user may want to take a look at the
+link:repository-layout.html[repository layout] document
+after finishing this tutorial.
+
+You have now created your first git repository. Of course, since it's
+empty, that's not very useful, so let's start populating it with data.
+
+
+Populating a git repository
+---------------------------
+
+We'll keep this simple and stupid, so we'll start off with populating a
+few trivial files just to get a feel for it.
+
+Start off with just creating any random files that you want to maintain
+in your git repository. We'll start off with a few bad examples, just to
+get a feel for how this works:
+
+------------------------------------------------
+$ echo "Hello World" >hello
+$ echo "Silly example" >example
+------------------------------------------------
+
+you have now created two files in your working tree (aka 'working directory'),
+but to actually check in your hard work, you will have to go through two steps:
+
+ - fill in the 'index' file (aka 'cache') with the information about your
+   working tree state.
+
+ - commit that index file as an object.
+
+The first step is trivial: when you want to tell git about any changes
+to your working tree, you use the `git-update-index` program. That
+program normally just takes a list of filenames you want to update, but
+to avoid trivial mistakes, it refuses to add new entries to the index
+(or remove existing ones) unless you explicitly tell it that you're
+adding a new entry with the `\--add` flag (or removing an entry with the
+`\--remove`) flag.
+
+So to populate the index with the two files you just created, you can do
+
+------------------------------------------------
+$ git-update-index --add hello example
+------------------------------------------------
+
+and you have now told git to track those two files.
+
+In fact, as you did that, if you now look into your object directory,
+you'll notice that git will have added two new objects to the object
+database. If you did exactly the steps above, you should now be able to do
+
+
+----------------
+$ ls .git/objects/??/*
+----------------
+
+and see two files:
+
+----------------
+.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238 
+.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
+----------------
+
+which correspond with the objects with names of `557db...` and
+`f24c7...` respectively.
+
+If you want to, you can use `git-cat-file` to look at those objects, but
+you'll have to use the object name, not the filename of the object:
+
+----------------
+$ git-cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
+----------------
+
+where the `-t` tells `git-cat-file` to tell you what the "type" of the
+object is. git will tell you that you have a "blob" object (i.e., just a
+regular file), and you can see the contents with
+
+----------------
+$ git-cat-file "blob" 557db03
+----------------
+
+which will print out "Hello World". The object `557db03` is nothing
+more than the contents of your file `hello`.
+
+[NOTE]
+Don't confuse that object with the file `hello` itself. The
+object is literally just those specific *contents* of the file, and
+however much you later change the contents in file `hello`, the object
+we just looked at will never change. Objects are immutable.
+
+[NOTE]
+The second example demonstrates that you can
+abbreviate the object name to only the first several
+hexadecimal digits in most places.
+
+Anyway, as we mentioned previously, you normally never actually take a
+look at the objects themselves, and typing long 40-character hex
+names is not something you'd normally want to do. The above digression
+was just to show that `git-update-index` did something magical, and
+actually saved away the contents of your files into the git object
+database.
+
+Updating the index did something else too: it created a `.git/index`
+file. This is the index that describes your current working tree, and
+something you should be very aware of. Again, you normally never worry
+about the index file itself, but you should be aware of the fact that
+you have not actually really "checked in" your files into git so far,
+you've only *told* git about them.
+
+However, since git knows about them, you can now start using some of the
+most basic git commands to manipulate the files or look at their status. 
+
+In particular, let's not even check in the two files into git yet, we'll
+start off by adding another line to `hello` first:
+
+------------------------------------------------
+$ echo "It's a new day for git" >>hello
+------------------------------------------------
+
+and you can now, since you told git about the previous state of `hello`, ask
+git what has changed in the tree compared to your old index, using the
+`git-diff-files` command:
+
+------------
+$ git-diff-files
+------------
+
+Oops. That wasn't very readable. It just spit out its own internal
+version of a `diff`, but that internal version really just tells you
+that it has noticed that "hello" has been modified, and that the old object
+contents it had have been replaced with something else.
+
+To make it readable, we can tell git-diff-files to output the
+differences as a patch, using the `-p` flag:
+
+------------
+$ git-diff-files -p
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+----
+
+i.e. the diff of the change we caused by adding another line to `hello`.
+
+In other words, `git-diff-files` always shows us the difference between
+what is recorded in the index, and what is currently in the working
+tree. That's very useful.
+
+A common shorthand for `git-diff-files -p` is to just write `git
+diff`, which will do the same thing.
+
+------------
+$ git diff
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+------------
+
+
+Committing git state
+--------------------
+
+Now, we want to go to the next stage in git, which is to take the files
+that git knows about in the index, and commit them as a real tree. We do
+that in two phases: creating a 'tree' object, and committing that 'tree'
+object as a 'commit' object together with an explanation of what the
+tree was all about, along with information of how we came to that state.
+
+Creating a tree object is trivial, and is done with `git-write-tree`.
+There are no options or other input: git-write-tree will take the
+current index state, and write an object that describes that whole
+index. In other words, we're now tying together all the different
+filenames with their contents (and their permissions), and we're
+creating the equivalent of a git "directory" object:
+
+------------------------------------------------
+$ git-write-tree
+------------------------------------------------
+
+and this will just output the name of the resulting tree, in this case
+(if you have done exactly as I've described) it should be
+
+----------------
+8988da15d077d4829fc51d8544c097def6644dbb
+----------------
+
+which is another incomprehensible object name. Again, if you want to,
+you can use `git-cat-file -t 8988d\...` to see that this time the object
+is not a "blob" object, but a "tree" object (you can also use
+`git-cat-file` to actually output the raw object contents, but you'll see
+mainly a binary mess, so that's less interesting).
+
+However -- normally you'd never use `git-write-tree` on its own, because
+normally you always commit a tree into a commit object using the
+`git-commit-tree` command. In fact, it's easier to not actually use
+`git-write-tree` on its own at all, but to just pass its result in as an
+argument to `git-commit-tree`.
+
+`git-commit-tree` normally takes several arguments -- it wants to know
+what the 'parent' of a commit was, but since this is the first commit
+ever in this new repository, and it has no parents, we only need to pass in
+the object name of the tree. However, `git-commit-tree`
+also wants to get a commit message
+on its standard input, and it will write out the resulting object name for the
+commit to its standard output.
+
+And this is where we create the `.git/refs/heads/master` file
+which is pointed at by `HEAD`. This file is supposed to contain
+the reference to the top-of-tree of the master branch, and since
+that's exactly what `git-commit-tree` spits out, we can do this
+all with a sequence of simple shell commands:
+
+------------------------------------------------
+$ tree=$(git-write-tree)
+$ 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.
+
+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
+you could have just written `git commit`
+instead, and it would have done the above magic scripting for you.
+
+
+Making a change
+---------------
+
+Remember how we did the `git-update-index` on file `hello` and then we
+changed `hello` afterward, and could compare the new state of `hello` with the
+state we saved in the index file? 
+
+Further, remember how I said that `git-write-tree` writes the contents
+of the *index* file to the tree, and thus what we just committed was in
+fact the *original* contents of the file `hello`, not the new ones. We did
+that on purpose, to show the difference between the index state, and the
+state in the working tree, and how they don't have to match, even
+when we commit things.
+
+As before, if we do `git-diff-files -p` in our git-tutorial project,
+we'll still see the same difference we saw last time: the index file
+hasn't changed by the act of committing anything. However, now that we
+have committed something, we can also learn to use a new command:
+`git-diff-index`.
+
+Unlike `git-diff-files`, which showed the difference between the index
+file and the working tree, `git-diff-index` shows the differences
+between a committed *tree* and either the index file or the working
+tree. In other words, `git-diff-index` wants a tree to be diffed
+against, and before we did the commit, we couldn't do that, because we
+didn't have anything to diff against. 
+
+But now we can do
+
+----------------
+$ git-diff-index -p HEAD
+----------------
+
+(where `-p` has the same meaning as it did in `git-diff-files`), and it
+will show us the same difference, but for a totally different reason. 
+Now we're comparing the working tree not against the index file,
+but against the tree we just wrote. It just so happens that those two
+are obviously the same, so we get the same result.
+
+Again, because this is a common operation, you can also just shorthand
+it with
+
+----------------
+$ git diff HEAD
+----------------
+
+which ends up doing the above for you.
+
+In other words, `git-diff-index` normally compares a tree against the
+working tree, but when given the `\--cached` flag, it is told to
+instead compare against just the index cache contents, and ignore the
+current working tree state entirely. Since we just wrote the index
+file to HEAD, doing `git-diff-index \--cached -p HEAD` should thus return
+an empty set of differences, and that's exactly what it does. 
+
+[NOTE]
+================
+`git-diff-index` really always uses the index for its
+comparisons, and saying that it compares a tree against the working
+tree is thus not strictly accurate. In particular, the list of
+files to compare (the "meta-data") *always* comes from the index file,
+regardless of whether the `\--cached` flag is used or not. The `\--cached`
+flag really only determines whether the file *contents* to be compared
+come from the working tree or not.
+
+This is not hard to understand, as soon as you realize that git simply
+never knows (or cares) about files that it is not told about
+explicitly. git will never go *looking* for files to compare, it
+expects you to tell it what the files are, and that's what the index
+is there for.
+================
+
+However, our next step is to commit the *change* we did, and again, to
+understand what's going on, keep in mind the difference between "working
+tree contents", "index file" and "committed tree". We have changes
+in the working tree that we want to commit, and we always have to
+work through the index file, so the first thing we need to do is to
+update the index cache:
+
+------------------------------------------------
+$ git-update-index hello
+------------------------------------------------
+
+(note how we didn't need the `\--add` flag this time, since git knew
+about the file already).
+
+Note what happens to the different `git-diff-\*` versions here. After
+we've updated `hello` in the index, `git-diff-files -p` now shows no
+differences, but `git-diff-index -p HEAD` still *does* show that the
+current state is different from the state we committed. In fact, now
+`git-diff-index` shows the same difference whether we use the `--cached`
+flag or not, since now the index is coherent with the working tree.
+
+Now, since we've updated `hello` in the index, we can commit the new
+version. We could do it by writing the tree by hand again, and
+committing the tree (this time we'd have to use the `-p HEAD` flag to
+tell commit that the HEAD was the *parent* of the new commit, and that
+this wasn't an initial commit any more), but you've done that once
+already, so let's just use the helpful script this time:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+which starts an editor for you to write the commit message and tells you
+a bit about what you have done.
+
+Write whatever message you want, and all the lines that start with '#'
+will be pruned out, and the rest will be used as the commit message for
+the change. If you decide you don't want to commit anything after all at
+this point (you can continue to edit things and update the index), you
+can just leave an empty message. Otherwise `git commit` will commit
+the change for you.
+
+You've now made your first real git commit. And if you're interested in
+looking at what `git commit` really does, feel free to investigate:
+it's a few very simple shell scripts to generate the helpful (?) commit
+message headers, and a few one-liners that actually do the
+commit itself (`git-commit`).
+
+
+Inspecting Changes
+------------------
+
+While creating changes is useful, it's even more useful if you can tell
+later what changed. The most useful command for this is another of the
+`diff` family, namely `git-diff-tree`.
+
+`git-diff-tree` can be given two arbitrary trees, and it will tell you the
+differences between them. Perhaps even more commonly, though, you can
+give it just a single commit object, and it will figure out the parent
+of that commit itself, and show the difference directly. Thus, to get
+the same diff that we've already seen several times, we can now do
+
+----------------
+$ git-diff-tree -p HEAD
+----------------
+
+(again, `-p` means to show the difference as a human-readable patch),
+and it will show what the last commit (in `HEAD`) actually changed.
+
+[NOTE]
+============
+Here is an ASCII art by Jon Loeliger that illustrates how
+various diff-\* commands compare things.
+
+                      diff-tree
+                       +----+
+                       |    |
+                       |    |
+                       V    V
+                    +-----------+
+                    | Object DB |
+                    |  Backing  |
+                    |   Store   |
+                    +-----------+
+                      ^    ^
+                      |    |
+                      |    |  diff-index --cached
+                      |    |
+          diff-index  |    V
+                      |  +-----------+
+                      |  |   Index   |
+                      |  |  "cache"  |
+                      |  +-----------+
+                      |    ^
+                      |    |
+                      |    |  diff-files
+                      |    |
+                      V    V
+                    +-----------+
+                    |  Working  |
+                    | Directory |
+                    +-----------+
+============
+
+More interestingly, you can also give `git-diff-tree` the `--pretty` flag,
+which tells it to also show the commit message and author and date of the
+commit, and you can tell it to show a whole series of diffs.
+Alternatively, you can tell it to be "silent", and not show the diffs at
+all, but just show the actual commit message.
+
+In fact, together with the `git-rev-list` program (which generates a
+list of revisions), `git-diff-tree` ends up being a veritable fount of
+changes. A trivial (but very useful) script called `git-whatchanged` is
+included with git which does exactly this, and shows a log of recent
+activities.
+
+To see the whole history of our pitiful little git-tutorial project, you
+can do
+
+----------------
+$ git log
+----------------
+
+which shows just the log messages, or if we want to see the log together
+with the associated patches use the more complex (and much more
+powerful)
+
+----------------
+$ git-whatchanged -p --root
+----------------
+
+and you will see exactly what has changed in the repository over its
+short history. 
+
+[NOTE]
+The `\--root` flag is a flag to `git-diff-tree` to tell it to
+show the initial aka 'root' commit too. Normally you'd probably not
+want to see the initial import diff, but since the tutorial project
+was started from scratch and is so small, we use it to make the result
+a bit more interesting.
+
+With that, you should now be having some inkling of what git does, and
+can explore on your own.
+
+[NOTE]
+Most likely, you are not directly using the core
+git Plumbing commands, but using Porcelain like Cogito on top
+of it. Cogito works a bit differently and you usually do not
+have to run `git-update-index` yourself for changed files (you
+do tell underlying git about additions and removals via
+`cg-add` and `cg-rm` commands). Just before you make a commit
+with `cg-commit`, Cogito figures out which files you modified,
+and runs `git-update-index` on them for you.
+
+
+Tagging a version
+-----------------
+
+In git, there are two kinds of tags, a "light" one, and an "annotated tag".
+
+A "light" tag is technically nothing more than a branch, except we put
+it in the `.git/refs/tags/` subdirectory instead of calling it a `head`.
+So the simplest form of tag involves nothing more than
+
+------------------------------------------------
+$ git tag my-first-tag
+------------------------------------------------
+
+which just writes the current `HEAD` into the `.git/refs/tags/my-first-tag`
+file, after which point you can then use this symbolic name for that
+particular state. You can, for example, do
+
+----------------
+$ git diff my-first-tag
+----------------
+
+to diff your current state against that tag (which at this point will
+obviously be an empty diff, but if you continue to develop and commit
+stuff, you can use your tag as an "anchor-point" to see what has changed
+since you tagged it.
+
+An "annotated tag" is actually a real git object, and contains not only a
+pointer to the state you want to tag, but also a small tag name and
+message, along with optionally a PGP signature that says that yes,
+you really did
+that tag. You create these annotated tags with either the `-a` or
+`-s` flag to `git tag`:
+
+----------------
+$ git tag -s <tagname>
+----------------
+
+which will sign the current `HEAD` (but you can also give it another
+argument that specifies the thing to tag, i.e., you could have tagged the
+current `mybranch` point by using `git tag <tagname> mybranch`).
+
+You normally only do signed tags for major releases or things
+like that, while the light-weight tags are useful for any marking you
+want to do -- any time you decide that you want to remember a certain
+point, just create a private tag for it, and you have a nice symbolic
+name for the state at that point.
+
+
+Copying repositories
+--------------------
+
+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`
+subdirectory. There is nothing else. What you see is what you got.
+
+[NOTE]
+You can tell git to split the git internal information from
+the directory that it tracks, but we'll ignore that for now: it's not
+how normal projects work, and it's really only meant for special uses.
+So the mental model of "the git information is always tied directly to
+the working tree that it describes" may not be technically 100%
+accurate, but it's a good model for all normal use.
+
+This has two implications: 
+
+ - if you grow bored with the tutorial repository you created (or you've
+   made a mistake and want to start all over), you can just do simple
++
+----------------
+$ rm -rf git-tutorial
+----------------
++
+and it will be gone. There's no external repository, and there's no
+history outside the project you created.
+
+ - if you want to move or duplicate a git repository, you can do so. There
+   is `git clone` command, but if all you want to do is just to
+   create a copy of your repository (with all the full history that
+   went along with it), you can do so with a regular
+   `cp -a git-tutorial new-git-tutorial`.
++
+Note that when you've moved or copied a git repository, your git index
+file (which caches various information, notably some of the "stat"
+information for the files involved) will likely need to be refreshed.
+So after you do a `cp -a` to create a new copy, you'll want to do
++
+----------------
+$ git-update-index --refresh
+----------------
++
+in the new repository to make sure that the index file is up-to-date.
+
+Note that the second point is true even across machines. You can
+duplicate a remote git repository with *any* regular copy mechanism, be it
+`scp`, `rsync` or `wget`.
+
+When copying a remote repository, you'll want to at a minimum update the
+index cache when you do this, and especially with other peoples'
+repositories you often want to make sure that the index cache is in some
+known state (you don't know *what* they've done and not yet checked in),
+so usually you'll precede the `git-update-index` with a
+
+----------------
+$ git-read-tree --reset HEAD
+$ git-update-index --refresh
+----------------
+
+which will force a total index re-build from the tree pointed to by `HEAD`.
+It resets the index contents to `HEAD`, and then the `git-update-index`
+makes sure to match up all index entries with the checked-out files.
+If the original repository had uncommitted changes in its
+working tree, `git-update-index --refresh` notices them and
+tells you they need to be updated.
+
+The above can also be written as simply
+
+----------------
+$ git reset
+----------------
+
+and in fact a lot of the common git command combinations can be scripted
+with the `git xyz` interfaces.  You can learn things by just looking
+at what the various git scripts do.  For example, `git reset` is the
+above two lines implemented in `git-reset`, but some things like
+`git status` and `git commit` are slightly more complex scripts around
+the basic git commands.
+
+Many (most?) public remote repositories will not contain any of
+the checked out files or even an index file, and will *only* contain the
+actual core git files. Such a repository usually doesn't even have the
+`.git` subdirectory, but has all the git files directly in the
+repository. 
+
+To create your own local live copy of such a "raw" git repository, you'd
+first create your own subdirectory for the project, and then copy the
+raw repository contents into the `.git` directory. For example, to
+create your own copy of the git repository, you'd do the following
+
+----------------
+$ mkdir my-git
+$ cd my-git
+$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
+----------------
+
+followed by 
+
+----------------
+$ git-read-tree HEAD
+----------------
+
+to populate the index. However, now you have populated the index, and
+you have all the git internal files, but you will notice that you don't
+actually have any of the working tree files to work on. To get
+those, you'd check them out with
+
+----------------
+$ git-checkout-index -u -a
+----------------
+
+where the `-u` flag means that you want the checkout to keep the index
+up-to-date (so that you don't have to refresh it afterward), and the
+`-a` flag means "check out all files" (if you have a stale copy or an
+older version of a checked out tree you may also need to add the `-f`
+flag first, to tell git-checkout-index to *force* overwriting of any old
+files). 
+
+Again, this can all be simplified with
+
+----------------
+$ git clone rsync://rsync.kernel.org/pub/scm/git/git.git/ my-git
+$ cd my-git
+$ git checkout
+----------------
+
+which will end up doing all of the above for you.
+
+You have now successfully copied somebody else's (mine) remote
+repository, and checked it out. 
+
+
+Creating a new branch
+---------------------
+
+Branches in git are really nothing more than pointers into the git
+object database from within the `.git/refs/` subdirectory, and as we
+already discussed, the `HEAD` branch is nothing but a symlink to one of
+these object pointers. 
+
+You can at any time create a new branch by just picking an arbitrary
+point in the project history, and just writing the SHA1 name of that
+object into a file under `.git/refs/heads/`. You can use any filename you
+want (and indeed, subdirectories), but the convention is that the
+"normal" branch is called `master`. That's just a convention, though,
+and nothing enforces it. 
+
+To show that as an example, let's go back to the git-tutorial repository we
+used earlier, and create a branch in it. You do that by simply just
+saying that you want to check out a new branch:
+
+------------
+$ git checkout -b mybranch
+------------
+
+will create a new branch based at the current `HEAD` position, and switch
+to it. 
+
+[NOTE]
+================================================
+If you make the decision to start your new branch at some
+other point in the history than the current `HEAD`, you can do so by
+just telling `git checkout` what the base of the checkout would be.
+In other words, if you have an earlier tag or branch, you'd just do
+
+------------
+$ git checkout -b mybranch earlier-commit
+------------
+
+and it would create the new branch `mybranch` at the earlier commit,
+and check out the state at that time.
+================================================
+
+You can always just jump back to your original `master` branch by doing
+
+------------
+$ git checkout master
+------------
+
+(or any other branch-name, for that matter) and if you forget which
+branch you happen to be on, a simple
+
+------------
+$ cat .git/HEAD
+------------
+
+will tell you where it's pointing.  To get the list of branches
+you have, you can say
+
+------------
+$ git branch
+------------
+
+which is nothing more than a simple script around `ls .git/refs/heads`.
+There will be asterisk in front of the branch you are currently on.
+
+Sometimes you may wish to create a new branch _without_ actually
+checking it out and switching to it. If so, just use the command
+
+------------
+$ git branch <branchname> [startingpoint]
+------------
+
+which will simply _create_ the branch, but will not do anything further. 
+You can then later -- once you decide that you want to actually develop
+on that branch -- switch to that branch with a regular `git checkout`
+with the branchname as the argument.
+
+
+Merging two branches
+--------------------
+
+One of the ideas of having a branch is that you do some (possibly
+experimental) work in it, and eventually merge it back to the main
+branch. So assuming you created the above `mybranch` that started out
+being the same as the original `master` branch, let's make sure we're in
+that branch, and do some work there.
+
+------------------------------------------------
+$ git checkout mybranch
+$ echo "Work, work, work" >>hello
+$ git commit -m 'Some work.' -i hello
+------------------------------------------------
+
+Here, we just added another line to `hello`, and we used a shorthand for
+doing both `git-update-index hello` and `git commit` by just giving the
+filename directly to `git commit`, with an `-i` flag (it tells
+git to 'include' that file in addition to what you have done to
+the index file so far when making the commit).  The `-m` flag is to give the
+commit log message from the command line.
+
+Now, to make it a bit more interesting, let's assume that somebody else
+does some work in the original branch, and simulate that by going back
+to the master branch, and editing the same file differently there:
+
+------------
+$ git checkout master
+------------
+
+Here, take a moment to look at the contents of `hello`, and notice how they
+don't contain the work we just did in `mybranch` -- because that work
+hasn't happened in the `master` branch at all. Then do
+
+------------
+$ echo "Play, play, play" >>hello
+$ echo "Lots of fun" >>example
+$ git commit -m 'Some fun.' -i hello example
+------------
+
+since the master branch is obviously in a much better mood.
+
+Now, you've got two branches, and you decide that you want to merge the
+work done. Before we do that, let's introduce a cool graphical tool that
+helps you view what's going on:
+
+----------------
+$ gitk --all
+----------------
+
+will show you graphically both of your branches (that's what the `\--all`
+means: normally it will just show you your current `HEAD`) and their
+histories. You can also see exactly how they came to be from a common
+source. 
+
+Anyway, let's exit `gitk` (`^Q` or the File menu), and decide that we want
+to merge the work we did on the `mybranch` branch into the `master`
+branch (which is currently our `HEAD` too). To do that, there's a nice
+script called `git merge`, which wants to know which branches you want
+to resolve and what the merge is all about:
+
+------------
+$ git merge "Merge work in mybranch" HEAD mybranch
+------------
+
+where the first argument is going to be used as the commit message if
+the merge can be resolved automatically.
+
+Now, in this case we've intentionally created a situation where the
+merge will need to be fixed up by hand, though, so git will do as much
+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`.
+
+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
+open `hello` in our editor (whatever that may be), and fix it up somehow.
+I'd suggest just making it so that `hello` contains all four lines:
+
+------------
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+------------
+
+and once you're happy with your manual merge, just do a
+
+------------
+$ git commit -i hello
+------------
+
+which will very loudly warn you that you're now committing a merge
+(which is correct, so never mind), and you can write a small merge
+message about your adventures in git-merge-land.
+
+After you're done, start up `gitk \--all` to see graphically what the
+history looks like. Notice that `mybranch` still exists, and you can
+switch to it, and continue to work with it if you want to. The
+`mybranch` branch will not contain the merge, but next time you merge it
+from the `master` branch, git will know how you merged it, so you'll not
+have to do _that_ merge again.
+
+Another useful tool, especially if you do not always work in X-Window
+environment, is `git show-branch`.
+
+------------------------------------------------
+$ git show-branch --topo-order master mybranch
+* [master] Merge work in mybranch
+ ! [mybranch] Some work.
+--
+-  [master] Merge work in mybranch
+*+ [mybranch] Some work.
+------------------------------------------------
+
+The first two lines indicate that it is showing the two branches
+and the first line of the commit log message from their
+top-of-the-tree commits, you are currently on `master` branch
+(notice the asterisk `\*` character), and the first column for
+the later output lines is used to show commits contained in the
+`master` branch, and the second column for the `mybranch`
+branch. Three commits are shown along with their log messages.
+All of them have non blank characters in the first column (`*`
+shows an ordinary commit on the current branch, `.` is a merge commit), which
+means they are now part of the `master` branch. Only the "Some
+work" commit has the plus `+` character in the second column,
+because `mybranch` has not been merged to incorporate these
+commits from the master branch.  The string inside brackets
+before the commit log message is a short name you can use to
+name the commit.  In the above example, 'master' and 'mybranch'
+are branch heads.  'master~1' is the first parent of 'master'
+branch head.  Please see 'git-rev-parse' documentation if you
+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 checkout mybranch
+$ git merge "Merge upstream changes." HEAD master
+------------
+
+This outputs something like this (the actual commit object names
+would be different)
+
+----------------
+Updating from ae3a2da... to a80b4aa....
+Fast forward
+ example |    1 +
+ hello   |    1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+----------------
+
+Because your branch did not contain anything more than what are
+already merged into the `master` branch, the resolve 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.
+
+You can run `gitk \--all` again to see how the commit ancestry
+looks like, or run `show-branch`, which tells you this.
+
+------------------------------------------------
+$ git show-branch master mybranch
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
+------------------------------------------------
+
+
+Merging external work
+---------------------
+
+It's usually much more common that you merge with somebody else than
+merging with your own branches, so it's worth pointing out that git
+makes that very easy too, and in fact, it's not that different from
+doing a `git merge`. In fact, a remote merge ends up being nothing
+more than "fetch the work from a remote repository into a temporary tag"
+followed by a `git merge`.
+
+Fetching from a remote repository is done by, unsurprisingly,
+`git fetch`:
+
+----------------
+$ git fetch <remote-repository>
+----------------
+
+One of the following transports can be used to name the
+repository to download from:
+
+Rsync::
+       `rsync://remote.machine/path/to/repo.git/`
++
+Rsync transport is usable for both uploading and downloading,
+but is completely unaware of what git does, and can produce
+unexpected results when you download from the public repository
+while the repository owner is uploading into it via `rsync`
+transport.  Most notably, it could update the files under
+`refs/` which holds the object name of the topmost commits
+before uploading the files in `objects/` -- the downloader would
+obtain head commit object name while that object itself is still
+not available in the repository.  For this reason, it is
+considered deprecated.
+
+SSH::
+       `remote.machine:/path/to/repo.git/` or
++
+`ssh://remote.machine/path/to/repo.git/`
++
+This transport can be used for both uploading and downloading,
+and requires you to have a log-in privilege over `ssh` to the
+remote machine.  It finds out the set of objects the other side
+lacks by exchanging the head commits both ends have and
+transfers (close to) minimum set of objects.  It is by far the
+most efficient way to exchange git objects between repositories.
+
+Local directory::
+       `/path/to/repo.git/`
++
+This transport is the same as SSH transport but uses `sh` to run
+both ends on the local machine instead of running other end on
+the remote machine via `ssh`.
+
+git Native::
+       `git://remote.machine/path/to/repo.git/`
++
+This transport was designed for anonymous downloading.  Like SSH
+transport, it finds out the set of objects the downstream side
+lacks and transfers (close to) minimum set of objects.
+
+HTTP(S)::
+       `http://remote.machine/path/to/repo.git/`
++
+Downloader from http and https URL
+first obtains the topmost commit object name from the remote site
+by looking at the specified refname under `repo.git/refs/` directory,
+and then tries to obtain the
+commit object by downloading from `repo.git/objects/xx/xxx\...`
+using the object name of that commit object.  Then it reads the
+commit object to find out its parent commits and the associate
+tree object; it repeats this process until it gets all the
+necessary objects.  Because of this behavior, they are
+sometimes also called 'commit walkers'.
++
+The 'commit walkers' are sometimes also called 'dumb
+transports', because they do not require any git aware smart
+server like git Native transport does.  Any stock HTTP server
+that does not even support directory index would suffice.  But
+you must prepare your repository with `git-update-server-info`
+to help dumb transport downloaders.
++
+There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
+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
+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
+simply do
+
+----------------
+$ git pull <remote-repository>
+----------------
+
+and optionally give a branch-name for the remote end as a second
+argument.
+
+[NOTE]
+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
+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:
+
+------------------------------------------------
+$ cat >.git/remotes/jgarzik <<\EOF
+URL: http://www.kernel.org/pub/scm/linux/git/jgarzik/
+EOF
+------------------------------------------------
+
+
+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?
+------------------------
+
+We said this tutorial shows what plumbing does to help you cope
+with the porcelain that isn't flushing, but we so far did not
+talk about how the merge really works.  If you are following
+this tutorial the first time, I'd suggest to skip to "Publishing
+your work" section and come back here later.
+
+OK, still with me?  To give us an example to look at, let's go
+back to the earlier repository with "hello" and "example" file,
+and bring ourselves back to the pre-merge state:
+
+------------
+$ git show-branch --more=3 master mybranch
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+-- [master] Merge work in mybranch
++* [master^2] Some work.
++* [master^] Some fun.
+------------
+
+Remember, before running `git merge`, our `master` head was at
+"Some fun." commit, while our `mybranch` head was at "Some
+work." commit.
+
+------------
+$ git checkout mybranch
+$ git reset --hard master^2
+$ git checkout master
+$ git reset --hard master^
+------------
+
+After rewinding, the commit structure should look like this:
+
+------------
+$ git show-branch
+* [master] Some fun.
+ ! [mybranch] Some work.
+--
+ + [mybranch] Some work.
+*  [master] Some fun.
+*+ [mybranch^] New day.
+------------
+
+Now we are ready to experiment with the merge by hand.
+
+`git merge` command, when merging two branches, uses 3-way merge
+algorithm.  First, it finds the common ancestor between them.
+The command it uses is `git-merge-base`:
+
+------------
+$ mb=$(git-merge-base HEAD mybranch)
+------------
+
+The command writes the commit object name of the common ancestor
+to the standard output, so we captured its output to a variable,
+because we will be using it in the next step.  BTW, the common
+ancestor commit is the "New day." commit in this case.  You can
+tell it by:
+
+------------
+$ git-name-rev $mb
+my-first-tag
+------------
+
+After finding out a common ancestor commit, the second step is
+this:
+
+------------
+$ git-read-tree -m -u $mb HEAD mybranch
+------------
+
+This is the same `git-read-tree` command we have already seen,
+but it takes three trees, unlike previous examples.  This reads
+the contents of each tree into different 'stage' in the index
+file (the first tree goes to stage 1, the second stage 2,
+etc.).  After reading three trees into three stages, the paths
+that are the same in all three stages are 'collapsed' into stage
+0.  Also paths that are the same in two of three stages are
+collapsed into stage 0, taking the SHA1 from either stage 2 or
+stage 3, whichever is different from stage 1 (i.e. only one side
+changed from the common ancestor).
+
+After 'collapsing' operation, paths that are different in three
+trees are left in non-zero stages.  At this point, you can
+inspect the index file with this command:
+
+------------
+$ git-ls-files --stage
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0      example
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
+------------
+
+In our example of only two files, we did not have unchanged
+files so only 'example' resulted in collapsing, but in real-life
+large projects, only small number of files change in one commit,
+and this 'collapsing' tends to trivially merge most of the paths
+fairly quickly, leaving only a handful the real changes in non-zero
+stages.
+
+To look at only non-zero stages, use `\--unmerged` flag:
+
+------------
+$ git-ls-files --unmerged
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
+------------
+
+The next step of merging is to merge these three versions of the
+file, using 3-way merge.  This is done by giving
+`git-merge-one-file` command as one of the arguments to
+`git-merge-index` command:
+
+------------
+$ git-merge-index git-merge-one-file hello
+Auto-merging hello.
+merge: warning: conflicts during merge
+ERROR: Merge conflict in hello.
+fatal: merge program failed
+------------
+
+`git-merge-one-file` script is called with parameters to
+describe those three versions, and is responsible to leave the
+merge results in the working tree.
+It is a fairly straightforward shell script, and
+eventually calls `merge` program from RCS suite to perform a
+file-level 3-way merge.  In this case, `merge` detects
+conflicts, and the merge result with conflict marks is left in
+the working tree..  This can be seen if you run `ls-files
+--stage` again at this point:
+
+------------
+$ git-ls-files --stage
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0      example
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello
+------------
+
+This is the state of the index file and the working file after
+`git merge` returns control back to you, leaving the conflicting
+merge for you to resolve.  Notice that the path `hello` is still
+unmerged, and what you see with `git diff` at this point is
+differences since stage 2 (i.e. your version).
+
+
+Publishing your work
+--------------------
+
+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?
+
+Your do your real work in your working tree that has your
+primary repository hanging under it as its `.git` subdirectory.
+You *could* make that repository accessible remotely and ask
+people to pull from it, but in practice that is not the way
+things are usually done. A recommended way is to have a public
+repository, make it reachable by other people, and when the
+changes you made in your primary working tree are in good shape,
+update the public repository from it. This is often called
+'pushing'.
+
+[NOTE]
+This public repository could further be mirrored, and that is
+how git repositories at `kernel.org` are managed.
+
+Publishing the changes from your local (private) repository to
+your remote (public) repository requires a write privilege on
+the remote machine. You need to have an SSH account there to
+run a single command, `git-receive-pack`.
+
+First, you need to create an empty repository on the remote
+machine that will house your public repository. This empty
+repository will be populated and be kept up-to-date by pushing
+into it later. Obviously, this repository creation needs to be
+done only once.
+
+[NOTE]
+`git push` uses a pair of programs,
+`git-send-pack` on your local machine, and `git-receive-pack`
+on the remote machine. The communication between the two over
+the network internally uses an SSH connection.
+
+Your private repository's git directory is usually `.git`, but
+your public repository is often named after the project name,
+i.e. `<project>.git`. Let's create such a public repository for
+project `my-git`. After logging into the remote machine, create
+an empty directory:
+
+------------
+$ 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`, we do things slightly differently:
+
+------------
+$ GIT_DIR=my-git.git git-init-db
+------------
+
+Make sure this directory is available for others you want your
+changes to be pulled by via the transport of your choice. Also
+you need to make sure that you have the `git-receive-pack`
+program on the `$PATH`.
+
+[NOTE]
+Many installations of sshd do not invoke your shell as the login
+shell when you directly run programs; what this means is that if
+your login shell is `bash`, only `.bashrc` is read and not
+`.bash_profile`. As a workaround, make sure `.bashrc` sets up
+`$PATH` so that you can run `git-receive-pack` program.
+
+[NOTE]
+If you plan to publish this repository to be accessed over http,
+you should do `chmod +x my-git.git/hooks/post-update` at this
+point.  This makes sure that every time you push into this
+repository, `git-update-server-info` is run.
+
+Your "public repository" is now ready to accept your changes.
+Come back to the machine you have your private repository. From
+there, run this command:
+
+------------
+$ git push <public-host>:/path/to/my-git.git master
+------------
+
+This synchronizes your public repository to match the named
+branch head (i.e. `master` in this case) and objects reachable
+from them in your current repository.
+
+As a real example, this is how I update my public git
+repository. Kernel.org mirror network takes care of the
+propagation to other publicly visible machines:
+
+------------
+$ git push master.kernel.org:/pub/scm/git/git.git/ 
+------------
+
+
+Packing your repository
+-----------------------
+
+Earlier, we saw that one file under `.git/objects/??/` directory
+is stored for each git object you create. This representation
+is efficient to create atomically and safely, but
+not so convenient to transport over the network. Since git objects are
+immutable once they are created, there is a way to optimize the
+storage by "packing them together". The command
+
+------------
+$ git repack
+------------
+
+will do it for you. If you followed the tutorial examples, you
+would have accumulated about 17 objects in `.git/objects/??/`
+directories by now. `git repack` tells you how many objects it
+packed, and stores the packed file in `.git/objects/pack`
+directory.
+
+[NOTE]
+You will see two files, `pack-\*.pack` and `pack-\*.idx`,
+in `.git/objects/pack` directory. They are closely related to
+each other, and if you ever copy them by hand to a different
+repository for whatever reason, you should make sure you copy
+them together. The former holds all the data from the objects
+in the pack, and the latter holds the index for random
+access.
+
+If you are paranoid, running `git-verify-pack` command would
+detect if you have a corrupt pack, but do not worry too much.
+Our programs are always perfect ;-).
+
+Once you have packed objects, you do not need to leave the
+unpacked objects that are contained in the pack file anymore.
+
+------------
+$ git prune-packed
+------------
+
+would remove them for you.
+
+You can try running `find .git/objects -type f` before and after
+you run `git prune-packed` if you are curious.  Also `git
+count-objects` would tell you how many unpacked objects are in
+your repository and how much space they are consuming.
+
+[NOTE]
+`git pull` is slightly cumbersome for HTTP transport, as a
+packed repository may contain relatively few objects in a
+relatively large pack. If you expect many HTTP pulls from your
+public repository you might want to repack & prune often, or
+never.
+
+If you run `git repack` again at this point, it will say
+"Nothing to pack". Once you continue your development and
+accumulate the changes, running `git repack` again will create a
+new pack, that contains objects created since you packed your
+repository the last time. We recommend that you pack your project
+soon after the initial import (unless you are starting your
+project from scratch), and then run `git repack` every once in a
+while, depending on how active your project is.
+
+When a repository is synchronized via `git push` and `git pull`
+objects packed in the source repository are usually stored
+unpacked in the destination, unless rsync transport is used.
+While this allows you to use different packing strategies on
+both ends, it also means you may need to repack both
+repositories every once in a while.
+
+
+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`).
+
+It should be stressed that this hierarchy is purely *informal*.
+There is nothing fundamental in git that enforces the "chain of
+patch flow" this hierarchy implies. You do not have to pull
+from only one remote repository.
+
+A recommended workflow for a "project lead" goes like this:
+
+1. Prepare your primary repository on your local machine. Your
+   work is done there.
+
+2. Prepare a public repository accessible to others.
++
+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`,
+`$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
+with `chmod +x post-update`.  This makes sure `git-update-server-info`
+keeps the necessary files up-to-date.
+
+3. Push into the public repository from your primary
+   repository.
+
+4. `git repack` the public repository. This establishes a big
+   pack that contains the initial set of objects as the
+   baseline, and possibly `git prune` if the transport
+   used for pulling from your repository supports packed
+   repositories.
+
+5. Keep working in your primary repository. Your changes
+   include modifications of your own, patches you receive via
+   e-mails, and merges resulting from pulling the "public"
+   repositories of your "subsystem maintainers".
++
+You can repack this private repository whenever you feel like.
+
+6. Push your changes to the public repository, and announce it
+   to the public.
+
+7. Every once in a while, "git repack" the public repository.
+   Go back to step 5. and continue working.
+
+
+A recommended work cycle for a "subsystem maintainer" who works
+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`.
+
+2. Prepare a public repository accessible to others, just like
+   the "project lead" person does.
+
+3. Copy over the packed files from "project lead" public
+   repository to your public repository, unless the "project
+   lead" repository lives on the same machine as yours.  In the
+   latter case, you can use `objects/info/alternates` file to
+   point at the repository you are borrowing from.
+
+4. Push into the public repository from your primary
+   repository. Run `git repack`, and possibly `git prune` if the
+   transport used for pulling from your repository supports
+   packed repositories.
+
+5. Keep working in your primary repository. Your changes
+   include modifications of your own, patches you receive via
+   e-mails, and merges resulting from pulling the "public"
+   repositories of your "project lead" and possibly your
+   "sub-subsystem maintainers".
++
+You can repack this private repository whenever you feel
+like.
+
+6. Push your changes to your public repository, and ask your
+   "project lead" and possibly your "sub-subsystem
+   maintainers" to pull from it.
+
+7. Every once in a while, `git repack` the public repository.
+   Go back to step 5. and continue working.
+
+
+A recommended work cycle for an "individual developer" who does
+not have a "public" repository is somewhat different. It goes
+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`.
+
+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`.
+
+4. Use `git cherry origin` to see which ones of your patches
+   were accepted, and/or use `git rebase origin` to port your
+   unmerged changes forward to the updated upstream.
+
+5. Use `git format-patch origin` to prepare patches for e-mail
+   submission to your upstream and send it out. Go back to
+   step 2. and continue.
+
+
+Working with Others, Shared Repository Style
+--------------------------------------------
+
+If you are coming from CVS background, the style of cooperation
+suggested in the previous section may be new to you. You do not
+have to worry. git supports "shared public repository" style of
+cooperation you are probably more familiar with as well.
+
+See link:cvs-migration.txt[git for CVS users] for the details.
+
+Bundling your work together
+---------------------------
+
+It is likely that you will be working on more than one thing at
+a time.  It is easy to manage those more-or-less independent tasks
+using branches with git.
+
+We have already seen how branches work previously,
+with "fun and work" example using two branches.  The idea is the
+same if there are more than two branches.  Let's say you started
+out from "master" head, and have some new code in the "master"
+branch, and two independent fixes in the "commit-fix" and
+"diff-fix" branches:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Release candidate #1
+---
+ +  [diff-fix] Fix rename detection.
+ +  [diff-fix~1] Better common substring algorithm.
++   [commit-fix] Fix commit message normalization.
+  * [master] Release candidate #1
+++* [diff-fix~2] Pretty-print messages.
+------------
+
+Both fixes are tested well, and at this point, you want to merge
+in both of them.  You could merge in 'diff-fix' first and then
+'commit-fix' next, like this:
+
+------------
+$ git merge 'Merge fix in diff-fix' master diff-fix
+$ git merge 'Merge fix in commit-fix' master commit-fix
+------------
+
+Which would result in:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Merge fix in commit-fix
+---
+  - [master] Merge fix in commit-fix
++ * [commit-fix] Fix commit message normalization.
+  - [master~1] Merge fix in diff-fix
+ +* [diff-fix] Fix rename detection.
+ +* [diff-fix~1] Better common substring algorithm.
+  * [master~2] Release candidate #1
+++* [master~3] Pretty-print messages.
+------------
+
+However, there is no particular reason to merge in one branch
+first and the other next, when what you have are a set of truly
+independent changes (if the order mattered, then they are not
+independent by definition).  You could instead merge those two
+branches into the current branch at once.  First let's undo what
+we just did and start over.  We would want to get the master
+branch before these two merges by resetting it to 'master~2':
+
+------------
+$ 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
+branch heads (this is known as 'making an Octopus'):
+
+------------
+$ git pull . commit-fix diff-fix
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+---
+  - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
++ * [commit-fix] Fix commit message normalization.
+ +* [diff-fix] Fix rename detection.
+ +* [diff-fix~1] Better common substring algorithm.
+  * [master~1] Release candidate #1
+++* [master~2] Pretty-print messages.
+------------
+
+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
+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
+those branches were not independent after all, and you should
+merge two at a time, documenting how you resolved the conflicts,
+and the reason why you preferred changes made in one side over
+the other.  Otherwise it would make the project history harder
+to follow, not easier.
+
+[ to be continued.. cvsimports ]
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
new file mode 100644 (file)
index 0000000..826d089
--- /dev/null
@@ -0,0 +1,304 @@
+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.
+
+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:
+
+  * 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.
+
+  * 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.
+
+Importing a CVS archive
+-----------------------
+
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path.  The magic command line is then
+
+-------------------------------------------
+$ git cvsimport -v -d <cvsroot> -C <destination> <module>
+-------------------------------------------
+
+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.
+
+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.
+
+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.
+
+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.
+
+Development Models
+------------------
+
+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.
+
+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.
+
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
+
+Emulating the CVS Development Model
+-----------------------------------
+
+Start with an ordinary git working directory containing the project, and
+remove the checked-out files, keeping just the bare .git directory:
+
+------------------------------------------------
+$ mv project/.git /pub/repo.git
+$ rm -r project/
+------------------------------------------------
+
+Next, give every team member read/write access to this repository.  One
+easy way to do this is to give all the team members ssh access to the
+machine where the repository is hosted.  If you don't want to give them a
+full shell on the machine, there is a restricted shell which only allows
+users to do git pushes and pulls; see gitlink:git-shell[1].
+
+Put all the committers should 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
+------------------------------------------------
+
+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 commiter 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:
+
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
+
+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.
+
+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:
+
+------------
+$ git push origin
+$ git push repo.shared.xz:/pub/scm/project.git/
+------------
+
+as long as the shared repository does not have any branches
+other than `master`.
+
+[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.
+============
+
+Advanced Shared Repository Management
+-------------------------------------
+
+Git allows you to specify scripts called "hooks" to be run at certain
+points.  You can use these, for example, to send all commits to the shared
+repository to a mailing list.  See link:hooks.txt[Hooks used by git].
+
+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
+------------
+
+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.
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
new file mode 100644 (file)
index 0000000..617d8f5
--- /dev/null
@@ -0,0 +1,197 @@
+The output format from "git-diff-index", "git-diff-tree" and
+"git-diff-files" are very similar.
+
+These commands all compare two sets of things; what is 
+compared differs:
+
+git-diff-index <tree-ish>::
+        compares the <tree-ish> and the files on the filesystem.
+
+git-diff-index --cached <tree-ish>::
+        compares the <tree-ish> and the index.
+
+git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]::
+        compares the trees named by the two arguments.
+
+git-diff-files [<pattern>...]::
+        compares the index and the files on the filesystem.
+
+
+An output line is formatted this way:
+
+------------------------------------------------
+in-place edit  :100644 100644 bcd1234... 0123456... M file0
+copy-edit      :100644 100644 abcd123... 1234567... C68 file1 file2
+rename-edit    :100644 100644 abcd123... 1234567... R86 file1 file3
+create         :000000 100644 0000000... 1234567... A file4
+delete         :100644 000000 1234567... 0000000... D file5
+unmerged       :000000 000000 0000000... 0000000... U file6
+------------------------------------------------
+
+That is, from the left to the right:
+
+. a colon.
+. mode for "src"; 000000 if creation or unmerged.
+. a space.
+. mode for "dst"; 000000 if deletion or unmerged.
+. a space.
+. sha1 for "src"; 0\{40\} if creation or unmerged.
+. a space.
+. sha1 for "dst"; 0\{40\} if creation, unmerged or "look at work tree".
+. a space.
+. status, followed by optional "score" number.
+. a tab or a NUL when '-z' option is used.
+. path for "src"
+. a tab or a NUL when '-z' option is used; only exists for C or R.
+. path for "dst"; only exists for C or R.
+. an LF or a NUL when '-z' option is used, to terminate the record.
+
+<sha1> is shown as all 0's if a file is new on the filesystem
+and it is out of sync with the index.
+
+Example:
+
+------------------------------------------------
+:100644 100644 5be4a4...... 000000...... M file.c
+------------------------------------------------
+
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
+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.
+
+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.
+
+1.   It is preceded with a "git diff" header, that looks like
+     this:
+
+     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,
+`/dev/null` is _not_ used in place of `a/` or `b/` filenames.
++
+When rename/copy is involved, `file1` and `file2` show the
+name of the source file of the rename/copy and the name of
+the file that rename/copy produces, respectively.
+
+2.   It is followed by one or more extended header lines:
+
+       old mode <mode>
+       new mode <mode>
+       deleted file mode <mode>
+       new file mode <mode>
+       copy from <path>
+       copy to <path>
+       rename from <path>
+       rename to <path>
+       similarity index <number>
+       dissimilarity index <number>
+       index <hash>..<hash> <mode>
+
+3.  TAB, LF, and backslash characters in pathnames are
+    represented as `\t`, `\n`, and `\\`, respectively.
+
+
+combined diff format
+--------------------
+
+git-diff-tree and git-diff-files can take '-c' or '--cc' option
+to produce 'combined diff', which looks like this:
+
+------------
+diff --combined describe.c
+@@@ +98,7 @@@
+   return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+  }
+
+- static void describe(char *arg)
+ -static void describe(struct commit *cmit, int last_one)
+++static void describe(char *arg, int last_one)
+  {
+ +     unsigned char sha1[20];
+ +     struct commit *cmit;
+------------
+
+Unlike the traditional 'unified' diff format, which shows two
+files A and B with a single column that has `-` (minus --
+appears in A but removed in B), `+` (plus -- missing in A but
+added to B), or ` ` (space -- unchanged) prefix, this format
+compares two or more files file1, file2,... with one file X, and
+shows how X differs from each of fileN.  One column for each of
+fileN is prepended to the output line to note how X's line is
+different from it.
+
+A `-` character in the column N means that the line appears in
+fileN but it does not appear in the last file.  A `+` character
+in the column N means that the line appears in the last file,
+and fileN does not have that line.
+
+In the above example output, the function signature was changed
+from both files (hence two `-` removals from both file1 and
+file2, plus `++` to mean one line that was added does not appear
+in either file1 nor file2).  Also two other lines are the same
+from file1 but do not appear in file2 (hence prefixed with ` +`).
+
+When shown by `git diff-tree -c`, it compares the parents of a
+merge commit with the merge result (i.e. file1..fileN are the
+parents).  When shown by `git diff-files -c`, it compares the
+two unresolved merge parents with the working tree file
+(i.e. file1 is stage 2 aka "our version", file2 is stage 3 aka
+"their version").
+
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
new file mode 100644 (file)
index 0000000..f523ec2
--- /dev/null
@@ -0,0 +1,98 @@
+-p::
+       Generate patch (see section on generating patches)
+
+-u::
+       Synonym for "-p".
+
+--patch-with-raw::
+       Generate patch but keep also the default raw diff output.
+
+--stat::
+       Generate a diffstat instead of a patch.
+
+--summary::
+       Output a condensed summary of extended header information
+       such as creations, renames and mode changes.
+
+--patch-with-stat::
+       Generate patch and prepend its diffstat.
+
+-z::
+       \0 line termination on output
+
+--name-only::
+       Show only names of changed files.
+
+--name-status::
+       Show only names and status of changed files.
+
+--full-index::
+       Instead of the first handful characters, show full
+       object name of pre- and post-image blob on the "index"
+       line when generating a patch format output.     
+
+--abbrev[=<n>]::
+       Instead of showing the full 40-byte hexadecimal object
+       name in diff-raw format output and diff-tree header
+       lines, show only handful hexdigits prefix.  This is
+       independent of --full-index option above, which controls
+       the diff-patch output format.  Non default number of
+       digits can be specified with --abbrev=<n>.
+
+-B::
+       Break complete rewrite changes into pairs of delete and create.
+
+-M::
+       Detect renames.
+
+-C::
+       Detect copies as well as renames.
+
+--diff-filter=[ACDMRTUXB*]::
+       Select only files that are Added (`A`), Copied (`C`),
+       Deleted (`D`), Modified (`M`), Renamed (`R`), have their
+       type (mode) changed (`T`), are Unmerged (`U`), are
+       Unknown (`X`), or have had their pairing Broken (`B`).
+       Any combination of the filter characters may be used.
+       When `*` (All-or-none) is added to the combination, all
+       paths are selected if there is any file that matches
+       other criteria in the comparison; if there is no file
+       that matches other criteria, nothing is selected.
+
+--find-copies-harder::
+       For performance reasons, by default, -C option finds copies only 
+       if the original file of the copy was modified in the same 
+       changeset.  This flag makes the command
+       inspect unmodified files as candidates for the source of
+       copy.  This is a very expensive operation for large
+       projects, so use it with caution.
+
+-l<num>::
+       -M and -C options require O(n^2) processing time where n
+       is the number of potential rename/copy targets.  This
+       option prevents rename/copy detection from running if
+       the number of rename/copy targets exceeds the specified
+       number.
+
+-S<string>::
+       Look for differences that contain the change in <string>.
+
+--pickaxe-all::
+       When -S finds a change, show all the changes in that
+       changeset, not just the files that contain the change
+       in <string>.
+
+--pickaxe-regex::
+       Make the <string> not a plain string but an extended POSIX
+       regex to match.
+
+-O<orderfile>::
+       Output the patch in the order specified in the
+       <orderfile>, which has one shell glob pattern per line.
+
+-R::
+       Swap two inputs; that is, show differences from index or
+       on-disk file to tree contents.
+
+For more detailed explanation on these common options, see also
+link:diffcore.html[diffcore documentation].
diff --git a/Documentation/diffcore.txt b/Documentation/diffcore.txt
new file mode 100644 (file)
index 0000000..cb4e562
--- /dev/null
@@ -0,0 +1,275 @@
+Tweaking diff output
+====================
+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
+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
+that are easier to understand than the conventional kind.
+
+
+The chain of operation
+----------------------
+
+The git-diff-* family works by first comparing two sets of
+files:
+
+ - git-diff-index compares contents of a "tree" object and the
+   working directory (when '\--cached' flag is not used) or a
+   "tree" object and the index file (when '\--cached' flag is
+   used);
+
+ - git-diff-files compares contents of the index file and the
+   working directory;
+
+ - 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
+called "diffcore", in a format similar to what is output when
+the -p option is not used.  E.g.
+
+------------------------------------------------
+in-place edit  :100644 100644 bcd1234... 0123456... M file0
+create         :000000 100644 0000000... 1234567... A file4
+delete         :100644 000000 1234567... 0000000... D file5
+unmerged       :000000 000000 0000000... 0000000... U file6
+------------------------------------------------
+
+The diffcore mechanism is fed a list of such comparison results
+(each of which is called "filepair", although at this point each
+of them talks about a single file), and transforms such a list
+into another list.  There are currently 6 such transformations:
+
+- diffcore-pathspec
+- diffcore-break
+- diffcore-rename
+- diffcore-merge-broken
+- diffcore-pickaxe
+- diffcore-order
+
+These are applied in sequence.  The set of filepairs git-diff-\*
+commands find are used as the input to diffcore-pathspec, and
+the output from diffcore-pathspec is used as the input to the
+next transformation.  The final result is then passed to the
+output routine and generates either diff-raw format (see Output
+format sections of the manual for git-diff-\* commands) or
+diff-patch format.
+
+
+diffcore-pathspec: For Ignoring Files Outside Our Consideration
+---------------------------------------------------------------
+
+The first transformation in the chain is diffcore-pathspec, and
+is controlled by giving the pathname parameters to the
+git-diff-* commands on the command line.  The pathspec is used
+to limit the world diff operates in.  It removes the filepairs
+outside the specified set of pathnames.  E.g. If the input set 
+of filepairs included:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M junkfile
+------------------------------------------------
+
+but the command invocation was "git-diff-files myfile", then the
+junkfile entry would be removed from the list because only "myfile"
+is under consideration.
+
+Implementation note.  For performance reasons, git-diff-tree
+uses the pathname parameters on the command line to cull set of
+filepairs it feeds the diffcore mechanism itself, and does not
+use diffcore-pathspec, but the end result is the same.
+
+
+diffcore-break: For Splitting Up "Complete Rewrites"
+----------------------------------------------------
+
+The second transformation in the chain is diffcore-break, and is
+controlled by the -B option to the git-diff-* commands.  This is
+used to detect a filepair that represents "complete rewrite" and
+break such filepair into two filepairs that represent delete and
+create.  E.g.  If the input contained this filepair:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M file0
+------------------------------------------------
+
+and if it detects that the file "file0" is completely rewritten,
+it changes it to:
+
+------------------------------------------------
+:100644 000000 bcd1234... 0000000... D file0
+:000000 100644 0000000... 0123456... A file0
+------------------------------------------------
+
+For the purpose of breaking a filepair, diffcore-break examines
+the extent of changes between the contents of the files before
+and after modification (i.e. the contents that have "bcd1234..."
+and "0123456..." as their SHA1 content ID, in the above
+example).  The amount of deletion of original contents and
+insertion of new material are added together, and if it exceeds
+the "break score", the filepair is broken into two.  The break
+score defaults to 50% of the size of the smaller of the original
+and the result (i.e. if the edit shrinks the file, the size of
+the result is used; if the edit lengthens the file, the size of
+the original is used), and can be customized by giving a number
+after "-B" option (e.g. "-B75" to tell it to use 75%).
+
+
+diffcore-rename: For Detection Renames and Copies
+-------------------------------------------------
+
+This transformation is used to detect renames and copies, and is
+controlled by the -M option (to detect renames) and the -C option
+(to detect copies as well) to the git-diff-* commands.  If the
+input contained these filepairs:
+
+------------------------------------------------
+:100644 000000 0123456... 0000000... D fileX
+:000000 100644 0000000... 0123456... A file0
+------------------------------------------------
+
+and the contents of the deleted file fileX is similar enough to
+the contents of the created file file0, then rename detection
+merges these filepairs and creates:
+
+------------------------------------------------
+:100644 100644 0123456... 0123456... R100 fileX file0
+------------------------------------------------
+
+When the "-C" option is used, the original contents of modified files,
+and deleted files (and also unmodified files, if the
+"\--find-copies-harder" option is used) are considered as candidates
+of the source files in rename/copy operation.  If the input were like
+these filepairs, that talk about a modified file fileY and a newly
+created file file0:
+
+------------------------------------------------
+:100644 100644 0123456... 1234567... M fileY
+:000000 100644 0000000... bcd3456... A file0
+------------------------------------------------
+
+the original contents of fileY and the resulting contents of
+file0 are compared, and if they are similar enough, they are
+changed to:
+
+------------------------------------------------
+:100644 100644 0123456... 1234567... M fileY
+:100644 100644 0123456... bcd3456... C100 fileY file0
+------------------------------------------------
+
+In both rename and copy detection, the same "extent of changes"
+algorithm used in diffcore-break is used to determine if two
+files are "similar enough", and can be customized to use
+a similarity score different from the default of 50% by giving a
+number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
+8/10 = 80%).
+
+Note.  When the "-C" option is used with `\--find-copies-harder`
+option, git-diff-\* commands feed unmodified filepairs to
+diffcore mechanism as well as modified ones.  This lets the copy
+detector consider unmodified files as copy source candidates at
+the expense of making it slower.  Without `\--find-copies-harder`,
+git-diff-\* commands can detect copies only if the file that was
+copied happened to have been modified in the same changeset.
+
+
+diffcore-merge-broken: For Putting "Complete Rewrites" Back Together
+--------------------------------------------------------------------
+
+This transformation is used to merge filepairs broken by
+diffcore-break, and not transformed into rename/copy by
+diffcore-rename, back into a single modification.  This always
+runs when diffcore-break is used.
+
+For the purpose of merging broken filepairs back, it uses a
+different "extent of changes" computation from the ones used by
+diffcore-break and diffcore-rename.  It counts only the deletion
+from the original, and does not count insertion.  If you removed
+only 10 lines from a 100-line document, even if you added 910
+new lines to make a new 1000-line document, you did not do a
+complete rewrite.  diffcore-break breaks such a case in order to
+help diffcore-rename to consider such filepairs as candidate of
+rename/copy detection, but if filepairs broken that way were not
+matched with other filepairs to create rename/copy, then this
+transformation merges them back into the original
+"modification".
+
+The "extent of changes" parameter can be tweaked from the
+default 80% (that is, unless more than 80% of the original
+material is deleted, the broken pairs are merged back into a
+single modification) by giving a second number to -B option,
+like these:
+
+* -B50/60 (give 50% "break score" to diffcore-break, use 60%
+  for diffcore-merge-broken).
+
+* -B/60 (the same as above, since diffcore-break defaults to 50%).
+
+Note that earlier implementation left a broken pair as a separate
+creation and deletion patches.  This was an unnecessary hack and
+the latest implementation always merges all the broken pairs
+back into modifications, but the resulting patch output is
+formatted differently for easier review in case of such
+a complete rewrite by showing the entire contents of old version
+prefixed with '-', followed by the entire contents of new
+version prefixed with '+'.
+
+
+diffcore-pickaxe: For Detecting Addition/Deletion of Specified String
+---------------------------------------------------------------------
+
+This transformation is used to find filepairs that represent
+changes that touch a specified string, and is controlled by the
+-S option and the `\--pickaxe-all` option to the git-diff-*
+commands.
+
+When diffcore-pickaxe is in use, it checks if there are
+filepairs whose "original" side has the specified string and
+whose "result" side does not.  Such a filepair represents "the
+string appeared in this changeset".  It also checks for the
+opposite case that loses the specified string.
+
+When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves
+only such filepairs that touch the specified string in its
+output.  When `\--pickaxe-all` is used, diffcore-pickaxe leaves all
+filepairs intact if there is such a filepair, or makes the
+output empty otherwise.  The latter behaviour is designed to
+make reviewing of the changes in the context of the whole
+changeset easier.
+
+
+diffcore-order: For Sorting the Output Based on Filenames
+---------------------------------------------------------
+
+This is used to reorder the filepairs according to the user's
+(or project's) taste, and is controlled by the -O option to the
+git-diff-* commands.
+
+This takes a text file each of whose lines is a shell glob
+pattern.  Filepairs that match a glob pattern on an earlier line
+in the file are output before ones that match a later line, and
+filepairs that do not match any glob pattern are output last.
+
+As an example, a typical orderfile for the core git probably
+would look like this:
+
+------------------------------------------------
+README
+Makefile
+Documentation
+*.h
+*.c
+t
+------------------------------------------------
+
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt
new file mode 100644 (file)
index 0000000..6745ab5
--- /dev/null
@@ -0,0 +1,468 @@
+Everyday GIT With 20 Commands Or So
+===================================
+
+GIT suite has over 100 commands, and the manual page for each of
+them discusses what the command does and how it is used in
+detail, but until you know what command should be used in order
+to achieve what you want to do, you cannot tell which manual
+page to look at, and if you know that already you do not need
+the manual.
+
+Does that mean you need to know all of them before you can use
+git?  Not at all.  Depending on the role you play, the set of
+commands you need to know is slightly different, but in any case
+what you need to learn is far smaller than the full set of
+commands to carry out your day-to-day work.  This document is to
+serve as a cheat-sheet and a set of pointers for people playing
+various roles.
+
+<<Basic Repository>> commands are needed by people who has a
+repository --- that is everybody, because every working tree of
+git is a repository.
+
+In addition, <<Individual Developer (Standalone)>> commands are
+essential for anybody who makes a commit, even for somebody who
+works alone.
+
+If you work with other people, you will need commands listed in
+<<Individual Developer (Participant)>> section as well.
+
+People who play <<Integrator>> role need to learn some more
+commands in addition to the above.
+
+<<Repository Administration>> commands are for system
+administrators who are responsible to care and feed git
+repositories to support developers.
+
+
+Basic Repository[[Basic Repository]]
+------------------------------------
+
+Everybody uses these commands to feed and care git repositories.
+
+  * gitlink:git-init-db[1] or gitlink:git-clone[1] to create a
+    new repository.
+
+  * gitlink:git-fsck-objects[1] to validate the repository.
+
+  * gitlink:git-prune[1] to garbage collect crufts in the
+    repository.
+
+  * gitlink:git-repack[1] to pack loose objects for efficiency.
+
+Examples
+~~~~~~~~
+
+Check health and remove cruft.::
++
+------------
+$ git fsck-objects <1>
+$ git prune
+$ git count-objects <2>
+$ git repack <3>
+$ git prune <4>
+------------
++
+<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
+of loose objects accumulation may be a good rule of thumb.
+<4> after repack, prune removes the duplicate loose objects.
+
+Repack a small project into single pack.::
++
+------------
+$ git repack -a -d <1>
+$ git prune
+------------
++
+<1> pack all the objects reachable from the refs into one pack
+and remove unneeded other packs
+
+
+Individual Developer (Standalone)[[Individual Developer (Standalone)]]
+----------------------------------------------------------------------
+
+A standalone individual developer does not exchange patches with
+other people, and works alone in a single repository, using the
+following commands.
+
+  * gitlink:git-show-branch[1] to see where you are.
+
+  * gitlink:git-log[1] to see what happened.
+
+  * gitlink:git-whatchanged[1] to find out where things have
+    come from.
+
+  * 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-diff[1] and gitlink:git-status[1] to see what
+    you are in the middle of doing.
+
+  * gitlink:git-commit[1] to advance the current branch.
+
+  * 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-rebase[1] to maintain topic branches.
+
+  * gitlink:git-tag[1] to mark known point.
+
+Examples
+~~~~~~~~
+
+Extract a tarball and create a working tree and a new repository to keep track of it.::
++
+------------
+$ tar zxf frotz.tar.gz
+$ cd frotz
+$ git-init-db
+$ git add . <1>
+$ git commit -m 'import of frotz source tree.'
+$ git tag v2.43 <2>
+------------
++
+<1> add everything under the current directory.
+<2> make a lightweight, unannotated tag.
+
+Create a topic branch and develop.::
++
+------------
+$ git checkout -b alsa-audio <1>
+$ 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 commit -a -s <5>
+$ edit/compile/test
+$ git reset --soft HEAD^ <6>
+$ 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 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".
+<3> you need to tell git if you added a new file; removal and
+modification will be caught if you do "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.
+<7> look at the changes since the premature commit we took back.
+<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
+<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.
+
+
+Individual Developer (Participant)[[Individual Developer (Participant)]]
+------------------------------------------------------------------------
+
+A developer working as a participant in a group project needs to
+learn how to communicate with others, and uses these commands in
+addition to the ones needed by a standalone developer.
+
+  * gitlink:git-clone[1] from the upstream to prime your local
+    repository.
+
+  * gitlink:git-pull[1] and gitlink:git-fetch[1] from "origin"
+    to keep up-to-date with the upstream.
+
+  * gitlink:git-push[1] to shared repository, if you adopt CVS
+    style shared repository workflow.
+
+  * gitlink:git-format-patch[1] to prepare e-mail submission, if
+    you adopt Linux kernel-style public forum workflow.
+
+Examples
+~~~~~~~~
+
+Clone the upstream and work on it.  Feed changes to upstream.::
++
+------------
+$ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6
+$ cd my2.6
+$ edit/compile/test; git commit -a -s <1>
+$ git format-patch origin <2>
+$ git pull <3>
+$ git whatchanged -p ORIG_HEAD.. arch/i386 include/asm-i386 <4>
+$ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5>
+$ git reset --hard ORIG_HEAD <6>
+$ git prune <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
+current branch.
+<4> immediately after pulling, look at the changes done upstream
+since last time we checked, only in the
+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/.
+
+
+Push into another repository.::
++
+------------
+satellite$ git clone mothership:frotz/.git 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$ edit/compile/test/commit
+satellite$ git push origin <4>
+
+mothership$ cd frotz
+mothership$ git checkout master
+mothership$ git pull . satellite <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.
+<5> on mothership machine, merge the work done on the satellite
+machine into the master branch.
+
+Branch off of a specific tag.::
++
+------------
+$ git checkout -b private2.6.14 v2.6.14 <1>
+$ edit/compile/test; git commit -a
+$ git checkout master
+$ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
+  git am -3 -k <2>
+------------
++
+<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
+without a formal "merging".
+
+
+Integrator[[Integrator]]
+------------------------
+
+A fairly central person acting as the integrator in a group
+project receives changes made by others, reviews and integrates
+them and publishes the result for others to use, using these
+commands in addition to the ones needed by participants.
+
+  * gitlink:git-am[1] to apply patches e-mailed in from your
+    contributors.
+
+  * gitlink:git-pull[1] to merge from your trusted lieutenants.
+
+  * gitlink:git-format-patch[1] to prepare and send suggested
+    alternative to contributors.
+
+  * gitlink:git-revert[1] to undo botched commits.
+
+  * gitlink:git-push[1] to publish the bleeding edge.
+
+
+Examples
+~~~~~~~~
+
+My typical GIT day.::
++
+------------
+$ git status <1>
+$ git show-branch <2>
+$ mailx <3>
+& s 2 3 4 5 ./+to-apply
+& s 7 8 ./+hold-linus
+& q
+$ git checkout 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 maint
+$ git cherry-pick master~4 <9>
+$ compile/test
+$ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10>
+$ git fetch ko && git show-branch master maint 'tags/ko-*' <11>
+$ git push ko <12>
+$ git push ko v0.99.9x <13>
+------------
++
+<1> see what I was in the middle of doing, if any.
+<2> see what topic branches I have and think about how ready
+they are.
+<3> read mails, save ones that are applicable, and save others
+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. 
+<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.
+<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
+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: maint:refs/tags/ko-maint
+Push: master
+Push: +pu
+Push: maint
+------------
++
+In the output from "git show-branch", "master" should have
+everything "ko-master" has.
+
+<12> push out the bleeding edge.
+<13> push the tag out, too.
+
+
+Repository Administration[[Repository Administration]]
+------------------------------------------------------
+
+A repository administrator uses the following tools to set up
+and maintain access to the repository by developers.
+
+  * gitlink:git-daemon[1] to allow anonymous download from
+    repository.
+
+  * gitlink:git-shell[1] can be used as a 'restricted login shell'
+    for shared central repository users.
+
+link:howto/update-hook-example.txt[update hook howto] has a good
+example of managing a shared central repository.
+
+
+Examples
+~~~~~~~~
+Run git-daemon to serve /pub/scm from inetd.::
++
+------------
+$ grep git /etc/inetd.conf
+git    stream  tcp     nowait  nobody \
+  /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm
+------------
++
+The actual configuration line should be on one line.
+
+Run git-daemon to serve /pub/scm from xinetd.::
++
+------------
+$ cat /etc/xinetd.d/git-daemon
+# default: off
+# description: The git server offers access to git repositories
+service git
+{
+        disable = no
+        type            = UNLISTED
+        port            = 9418
+        socket_type     = stream
+        wait            = no
+        user            = nobody
+        server          = /usr/bin/git-daemon
+        server_args     = --inetd --syslog --export-all --base-path=/pub/scm
+        log_on_failure  += USERID
+}
+------------
++
+Check your xinetd(8) documentation and setup, this is from a Fedora system.
+Others might be different.
+
+Give push/pull only access to developers.::
++
+------------
+$ grep git /etc/passwd <1>
+alice:x:1000:1000::/home/alice:/usr/bin/git-shell
+bob:x:1001:1001::/home/bob:/usr/bin/git-shell
+cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
+david:x:1003:1003::/home/david:/usr/bin/git-shell
+$ grep git /etc/shells <2>
+/usr/bin/git-shell
+------------
++
+<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
+get an ssh access to the machine.
+<2> in many distributions /etc/shells needs to list what is used
+as the login shell.
+
+CVS-style shared repository.::
++
+------------
+$ grep git /etc/group <1>
+git:x:9418:alice,bob,cindy,david
+$ cd /home/devo.git
+$ ls -l <2>
+  lrwxrwxrwx   1 david git    17 Dec  4 22:40 HEAD -> refs/heads/master
+  drwxrwsr-x   2 david git  4096 Dec  4 22:40 branches
+  -rw-rw-r--   1 david git    84 Dec  4 22:40 config
+  -rw-rw-r--   1 david git    58 Dec  4 22:40 description
+  drwxrwsr-x   2 david git  4096 Dec  4 22:40 hooks
+  -rw-rw-r--   1 david git 37504 Dec  4 22:40 index
+  drwxrwsr-x   2 david git  4096 Dec  4 22:40 info
+  drwxrwsr-x   4 david git  4096 Dec  4 22:40 objects
+  drwxrwsr-x   4 david git  4096 Nov  7 14:58 refs
+  drwxrwsr-x   2 david git  4096 Dec  4 22:40 remotes
+$ ls -l hooks/update <3>
+  -r-xr-xr-x   1 david git  3536 Dec  4 22:40 update
+$ cat info/allowed-users <4>
+refs/heads/master      alice\|cindy
+refs/heads/doc-update  bob
+refs/tags/v[0-9]*      david
+------------
++
+<1> place the developers into the same git group.
+<2> and make the shared repository writable by the group.
+<3> use update-hook example by Carl from Documentation/howto/
+for branch policy control.
+<4> alice and cindy can push into master, only bob can push into doc-update.
+david is the release manager and is the only person who can
+create and push version tags.
+
+HTTP server to support dumb protocol transfer.::
++
+------------
+dev$ git update-server-info <1>
+dev$ ftp user@isp.example.com <2>
+ftp> cp -r .git /home/user/myproject.git
+------------
++
+<1> make sure your info/refs and objects/info/packs are up-to-date
+<2> upload to public HTTP server hosted by your ISP.
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
new file mode 100644 (file)
index 0000000..13f34d3
--- /dev/null
@@ -0,0 +1,41 @@
+-a, \--append::
+       Append ref names and object names of fetched refs to the
+       existing contents of `.git/FETCH_HEAD`.  Without this
+       option old data in `.git/FETCH_HEAD` will be overwritten.
+
+\--upload-pack <upload-pack>::
+        When given, and the repository to fetch from is handled
+        by 'git-fetch-pack', '--exec=<upload-pack>' is passed to
+        the command to specify non-default path for the command
+        run on the other end.
+
+-f, \--force::
+       When `git-fetch` is used with `<rbranch>:<lbranch>`
+       refspec, it refuses to update the local branch
+       `<lbranch>` unless the remote branch `<rbranch>` it
+       fetches is a descendant of `<lbranch>`.  This option
+       overrides that check.
+
+\--no-tags::
+       By default, `git-fetch` fetches tags that point at
+       objects that are downloaded from the remote repository
+       and stores them locally.  This option disables this
+       automatic tag following.
+
+-t, \--tags::
+       Most of the tags are fetched automatically as branch
+       heads are downloaded, but tags that do not point at
+       objects reachable from the branch heads that are being
+       tracked will not be fetched by this mechanism.  This
+       flag lets all tags and their associated objects be
+       downloaded.
+
+-k, \--keep::
+       Keep downloaded pack.
+
+-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.
+
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
new file mode 100644 (file)
index 0000000..6342ea3
--- /dev/null
@@ -0,0 +1,86 @@
+git-add(1)
+==========
+
+NAME
+----
+git-add - Add files to the index file
+
+SYNOPSIS
+--------
+'git-add' [-n] [-v] [--] <file>...
+
+DESCRIPTION
+-----------
+A simple wrapper for git-update-index to add files to the index,
+for people used to do "cvs add".
+
+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]).
+
+-n::
+        Don't actually add the file(s), just show if they exist.
+
+-v::
+        Be verbose.
+
+\--::
+       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.
++
+Note that the asterisk `\*` is quoted from the shell in this
+example; this lets the command to include the files from
+subdirectories of `Documentation/` directory.
+
+git-add git-*.sh::
+
+       Adds all git-*.sh scripts that are not in the index.
+       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.
+
+See Also
+--------
+gitlink:git-rm[1]
+gitlink:git-ls-files[1]
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
new file mode 100644 (file)
index 0000000..910457d
--- /dev/null
@@ -0,0 +1,102 @@
+git-am(1)
+=========
+
+NAME
+----
+git-am - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+[verse]
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+         [--interactive] [--whitespace=<option>] <mbox>...
+'git-am' [--skip | --resolved]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+OPTIONS
+-------
+--signoff::
+       Add `Signed-off-by:` line to the commit message, using
+       the committer identity of yourself.
+
+--dotest=<dir>::
+       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
+       gitlink:git-mailinfo[1]).
+
+--binary::
+       Pass `--allow-binary-replacement` flag to `git-apply`
+       (see gitlink:git-apply[1]).
+
+--3way::
+       When the patch does not apply cleanly, fall back on
+       3-way merge, if the patch records the identity of blobs
+       it is supposed to apply to, and we have those blobs
+       locally.
+
+--skip::
+       Skip the current patch.  This is only meaningful when
+       restarting an aborted patch.
+
+--whitespace=<option>::
+       This flag is passed to the `git-apply` program that applies
+       the patch.
+
+--interactive::
+       Run interactively, just like git-applymbox.
+
+--resolved::
+       After a patch failure (e.g. attempting to apply
+       conflicting patch), the user has applied it by hand and
+       the index file stores the result of the application.
+       Make a commit using the authorship and commit log
+       extracted from the e-mail message and the current index
+       file, and continue.
+
+DISCUSSION
+----------
+
+When initially invoking it, you give it names of the mailboxes
+to crunch.  Upon seeing the first patch that does not apply, it
+aborts in the middle, just like 'git-applymbox' does.  You can
+recover from this in one of two ways:
+
+. skip the current one by re-running the command with '--skip'
+  option.
+
+. hand resolve the conflict in the working directory, and update
+  the index file to bring it in a state that the patch should
+  have produced.  Then run the command with '--resolved' option.
+
+The command refuses to process new mailboxes while `.dotest`
+directory exists, so if you decide to start over from scratch,
+run `rm -f .dotest` before running the command with mailbox
+names.
+
+
+SEE ALSO
+--------
+gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt
new file mode 100644 (file)
index 0000000..7baf731
--- /dev/null
@@ -0,0 +1,44 @@
+git-annotate(1)
+===============
+
+NAME
+----
+git-annotate - Annotate file lines with commit info
+
+SYNOPSIS
+--------
+git-annotate [options] file [revision]
+
+DESCRIPTION
+-----------
+Annotates each line in the given file with information from the commit
+which introduced the line. Optionally annotate from a given revision.
+
+OPTIONS
+-------
+-l, --long::
+       Show long rev (Defaults off).
+
+-t, --time::
+       Show raw timestamp (Defaults off).
+
+-r, --rename::
+       Follow renames (Defaults on).
+
+-S, --rev-file <revs-file>::
+       Use revs from revs-file instead of calling git-rev-list.
+
+-h, --help::
+       Show help message.
+
+SEE ALSO
+--------
+gitlink:git-blame[1]
+
+AUTHOR
+------
+Written by Ryan Anderson <ryan@michonline.com>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
new file mode 100644 (file)
index 0000000..9cc7c74
--- /dev/null
@@ -0,0 +1,147 @@
+git-apply(1)
+============
+
+NAME
+----
+git-apply - Apply patch on a git index file and a work tree
+
+
+SYNOPSIS
+--------
+[verse]
+'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
+         [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM]
+         [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>]
+         [<patch>...]
+
+DESCRIPTION
+-----------
+Reads supplied diff output and applies it on a git index file
+and a work tree.
+
+OPTIONS
+-------
+<patch>...::
+       The files to read patch from.  '-' can be used to read
+       from the standard input.
+
+--stat::
+       Instead of applying the patch, output diffstat for the
+       input.  Turns off "apply".
+
+--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".
+
+--summary::
+       Instead of applying the patch, output a condensed
+       summary of information obtained from git diff extended
+       headers, such as creations, renames and mode changes.
+       Turns off "apply".
+
+--check::
+       Instead of applying the patch, see if the patch is
+       applicable to the current work tree and/or the index
+       file and detects errors.  Turns off "apply".
+
+--index::
+       When --check is in effect, or when applying the patch
+       (which is the default when none of the options that
+       disables it is in effect), make sure the patch is
+       applicable to what the current index file records.  If
+       the file to be patched in the work tree is not
+       up-to-date, it is flagged as an error.  This flag also
+       causes the index file to be updated.
+
+--index-info::
+       Newer git-diff output has embedded 'index information'
+       for each blob to help identify the original version that
+       the patch applies to.  When this flag is given, and if
+       the original version of the blob is available locally,
+       outputs information about them to the standard output.
+
+-z::
+       When showing the index information, do not munge paths,
+       but use NUL terminated machine readable format.  Without
+       this flag, the pathnames output will have TAB, LF, and
+       backslash characters replaced with `\t`, `\n`, and `\\`,
+       respectively.
+
+-p<n>::
+       Remove <n> leading slashes from traditional diff paths. The
+       default is 1.
+
+-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 most match.  By default no context is
+       ever ignored.
+
+--apply::
+       If you use any of the options marked ``Turns off
+       "apply"'' above, git-apply reads and outputs the
+       information you asked without actually applying the
+       patch.  Give this flag after those flags to also apply
+       the patch.
+
+--no-add::
+       When applying a patch, ignore additions made by the
+       patch.  This can be used to extract common part between
+       two files by first running `diff` on them and applying
+       the result with this option, which would apply the
+       deletion part but not addition part.
+
+--allow-binary-replacement::
+       When applying a patch, which is a git-enhanced patch
+       that was prepared to record the pre- and post-image object
+       name in full, and the path being patched exactly matches
+       the object the patch applies to (i.e. "index" line's
+       pre-image object name is what is in the working tree),
+       and the post-image object is available in the object
+       database, use the post-image object as the patch
+       result.  This allows binary files to be patched in a
+       very limited way.
+
+--whitespace=<option>::
+       When applying a patch, detect a new or modified line
+       that ends with trailing whitespaces (this includes a
+       line that solely consists of whitespaces).  By default,
+       the command outputs warning messages and applies the
+       patch.
+       When `git-apply` is used for statistics and not applying a
+       patch, it defaults to `nowarn`.
+       You can use different `<option>` to control this
+       behavior:
++
+* `nowarn` turns off the trailing whitespace warning.
+* `warn` outputs warnings for a few such errors, but applies the
+  patch (default).
+* `error` outputs warnings for a few such errors, and refuses
+  to apply the patch.
+* `error-all` is similar to `error` but shows all errors.
+* `strip` outputs warnings for a few such errors, strips out the
+  trailing whitespaces and applies the patch.
+
+
+Configuration
+-------------
+
+apply.whitespace::
+       When no `--whitespace` flag is given from the command
+       line, this configuration item is used as the default.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt
new file mode 100644 (file)
index 0000000..f74c6a4
--- /dev/null
@@ -0,0 +1,92 @@
+git-applymbox(1)
+================
+
+NAME
+----
+git-applymbox - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+
+OPTIONS
+-------
+-q::
+       Apply patches interactively.  The user will be given
+       opportunity to edit the log message and the patch before
+       attempting to apply it.
+
+-k::
+       Usually the program 'cleans up' the Subject: header line
+       to extract the title line for the commit log message,
+       among which (1) remove 'Re:' or 're:', (2) leading
+       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+       then prepends "[PATCH] ".  This flag forbids this
+       munging, and is most useful when used to read back 'git
+       format-patch --mbox' output.
+
+-m::
+       Patches are applied with `git-apply` command, and unless
+       it cleanly applies without fuzz, the processing fails.
+       With this flag, if a tree that the patch applies cleanly
+       is found in a repository, the patch is applied to the
+       tree and then a 3-way merge between the resulting tree
+       and the current tree.
+
+-u::
+       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.
+
+-c .dotest/<num>::
+       When the patch contained in an e-mail does not cleanly
+       apply, the command exits with an error message. The
+       patch and extracted message are found in .dotest/, and
+       you could re-run 'git applymbox' with '-c .dotest/<num>'
+       flag to restart the process after inspecting and fixing
+       them.
+
+<mbox>::
+       The name of the file that contains the e-mail messages
+       with patches.  This file should be in the UNIX mailbox
+       format.  See 'SubmittingPatches' document to learn about
+       the formatting convention for e-mail submission.
+
+<signoff>::
+       The name of the file that contains your "Signed-off-by"
+       line.  See 'SubmittingPatches' document to learn what
+       "Signed-off-by" line means.  You can also just say
+       'yes', 'true', 'me', or 'please' to use an automatically
+       generated "Signed-off-by" line based on your committer
+       identity.
+
+
+SEE ALSO
+--------
+gitlink:git-am[1], gitlink:git-applypatch[1].
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt
new file mode 100644 (file)
index 0000000..2b1ff14
--- /dev/null
@@ -0,0 +1,50 @@
+git-applypatch(1)
+=================
+
+NAME
+----
+git-applypatch - Apply one patch extracted from an e-mail
+
+
+SYNOPSIS
+--------
+'git-applypatch' <msg> <patch> <info> [<signoff>]
+
+DESCRIPTION
+-----------
+Takes three files <msg>, <patch>, and <info> prepared from an
+e-mail message by 'git-mailinfo', and creates a commit.  It is
+usually not necessary to use this command directly.
+
+This command can run `applypatch-msg`, `pre-applypatch`, and
+`post-applypatch` hooks.  See link:hooks.html[hooks] for more
+information.
+
+
+OPTIONS
+-------
+<msg>::
+       Commit log message (sans the first line, which comes
+       from e-mail Subject stored in <info>).
+
+<patch>::
+       The patch to apply.
+
+<info>::
+       Author and subject information extracted from e-mail,
+       used on "author" line and as the first line of the
+       commit log message.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
new file mode 100644 (file)
index 0000000..5a13187
--- /dev/null
@@ -0,0 +1,106 @@
+git-archimport(1)
+=================
+
+NAME
+----
+git-archimport - Import an Arch repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+'git-archimport' [-h] [-v] [-o] [-a] [-f] [-T] [-D depth] [-t tempdir]
+               <archive/branch> [ <archive/branch> ]
+
+DESCRIPTION
+-----------
+Imports a project from one or more Arch repositories. It will follow branches
+and repositories within the namespaces defined by the <archive/branch>
+parameters supplied. If it cannot find the remote branch a merge comes from
+it will just import it as a regular commit. If it can find it, it will mark it 
+as a merge whenever possible (see discussion below). 
+
+The script expects you to provide the key roots where it can start the import 
+from an 'initial import' or 'tag' type of Arch commit. It will follow and 
+import new branches within the provided roots. 
+
+It expects to be dealing with one project only. If it sees 
+branches that have different roots, it will refuse to run. In that case, 
+edit your <archive/branch> parameters to define clearly the scope of the 
+import. 
+
+`git-archimport` uses `tla` extensively in the background to access the 
+Arch repository.
+Make sure you have a recent version of `tla` available in the path. `tla` must
+know about the repositories you pass to `git-archimport`. 
+
+For the initial import `git-archimport` expects to find itself in an empty 
+directory. To follow the development of a project that uses Arch, rerun 
+`git-archimport` with the same parameters as the initial import to perform 
+incremental imports.
+
+MERGES
+------
+Patch merge data from Arch is used to mark merges in git as well. git 
+does not care much about tracking patches, and only considers a merge when a
+branch incorporates all the commits since the point they forked. The end result
+is that git will have a good idea of how far branches have diverged. So the 
+import process does lose some patch-trading metadata.
+
+Fortunately, when you try and merge branches imported from Arch, 
+git will find a good merge base, and it has a good chance of identifying 
+patches that have been traded out-of-sequence between the branches. 
+
+OPTIONS
+-------
+
+-h::
+       Display usage.
+
+-v::
+       Verbose output. 
+
+-T::
+       Many tags. Will create a tag for every commit, reflecting the commit 
+       name in the Arch repository.
+
+-f::
+       Use the fast patchset import strategy.  This can be significantly
+       faster for large trees, but cannot handle directory renames or
+       permissions changes.  The default strategy is slow and safe.
+
+-o::
+       Use this for compatibility with old-style branch names used by
+       earlier versions of git-archimport.  Old-style branch names
+       were category--branch, whereas new-style branch names are
+       archive,category--branch--version.
+
+-D <depth>::
+       Follow merge ancestry and attempt to import trees that have been
+       merged from.  Specify a depth greater than 1 if patch logs have been
+       pruned.
+
+-a::
+       Attempt to auto-register archives at http://mirrors.sourcecontrol.net
+       This is particularly useful with the -D option.
+
+-t <tmpdir>::
+       Override the default tempdir.
+
+
+<archive/branch>::
+       Archive/branch identifier in a format that `tla log` understands. 
+
+
+Author
+------
+Written by Martin Langhoff <martin@catalyst.net.nz>.
+
+Documentation
+--------------
+Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
new file mode 100644 (file)
index 0000000..ac4b496
--- /dev/null
@@ -0,0 +1,136 @@
+git-bisect(1)
+=============
+
+NAME
+----
+git-bisect - Find the change that introduced a bug
+
+
+SYNOPSIS
+--------
+'git bisect' <subcommand> <options> 
+
+DESCRIPTION
+-----------
+The command takes various subcommands, and different options
+depending on the subcommand:
+
+ git bisect start [<paths>...]
+ git bisect bad <rev>
+ git bisect good <rev>
+ git bisect reset [<branch>]
+ git bisect visualize
+ git bisect replay <logfile>
+ git bisect log
+
+This command uses 'git-rev-list --bisect' option to help drive
+the binary search process to find which change introduced a bug,
+given an old "good" commit object name and a later "bad" commit
+object name.
+
+The way you use it is:
+
+------------------------------------------------
+$ git bisect start
+$ git bisect bad                       # Current version is bad
+$ git bisect good v2.6.13-rc2          # v2.6.13-rc2 was the last version
+                                       # tested that was good
+------------------------------------------------
+
+When you give at least one bad and one good versions, it will
+bisect the revision tree and say something like:
+
+------------------------------------------------
+Bisecting: 675 revisions left to test after this
+------------------------------------------------
+
+and check out the state in the middle. Now, compile that kernel, and boot
+it. Now, let's say that this booted kernel works fine, then just do
+
+------------------------------------------------
+$ git bisect good                      # this one is good
+------------------------------------------------
+
+which will now say
+
+------------------------------------------------
+Bisecting: 337 revisions left to test after this
+------------------------------------------------
+
+and you continue along, compiling that one, testing it, and depending on
+whether it is good or bad, you say "git bisect good" or "git bisect bad",
+and ask for the next bisection.
+
+Until you have no more left, and you'll have been left with the first bad
+kernel rev in "refs/bisect/bad".
+
+Oh, and then after you want to reset to the original head, do a
+
+------------------------------------------------
+$ git bisect reset
+------------------------------------------------
+
+to get back to the master branch, instead of being in one of the bisection
+branches ("git bisect start" will do that for you too, actually: it will
+reset the bisection state, and before it does that it checks that you're
+not using some old bisection branch).
+
+During the bisection process, you can say
+
+------------
+$ git bisect visualize
+------------
+
+to see the currently remaining suspects in `gitk`.
+
+The good/bad input is logged, and `git bisect
+log` shows what you have done so far.  You can truncate its
+output somewhere and save it in a file, and run
+
+------------
+$ git bisect replay that-file
+------------
+
+if you find later you made a mistake telling good/bad about a
+revision.
+
+If in a middle of bisect session, you know what the bisect
+suggested to try next is not a good one to test (e.g. the change
+the commit introduces is known not to work in your environment
+and you know it does not have anything to do with the bug you
+are chasing), you may want to find a near-by commit and try that
+instead.  It goes something like this:
+
+------------
+$ git bisect good/bad                  # previous round was good/bad.
+Bisecting: 337 revisions left to test after this
+$ git bisect visualize                 # oops, that is uninteresting.
+$ git reset --hard HEAD~3              # try 3 revs before what
+                                       # was suggested
+------------
+
+Then compile and test the one you chose to try.  After that,
+tell bisect what the result was as usual.
+
+You can further cut down the number of trials if you know what
+part of the tree is involved in the problem you are tracking
+down, by giving paths parameters when you say `bisect start`,
+like this:
+
+------------
+$ git bisect start arch/i386 include/asm-i386
+------------
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt
new file mode 100644 (file)
index 0000000..0a1fa00
--- /dev/null
@@ -0,0 +1,42 @@
+git-blame(1)
+============
+
+NAME
+----
+git-blame - Blame file lines on commits
+
+SYNOPSIS
+--------
+git-blame file [options] file [revision]
+
+DESCRIPTION
+-----------
+Annotates each line in the given file with information from the commit
+which introduced the line. Start annotation from the given revision.
+
+OPTIONS
+-------
+-c, --compatibility::
+       Use the same output mode as git-annotate (Default: off).
+
+-l, --long::
+       Show long rev (Defaults off).
+
+-S, --rev-file <revs-file>::
+       Use revs from revs-file instead of calling git-rev-list.
+
+-h, --help::
+       Show help message.
+
+
+SEE ALSO
+--------
+gitlink:git-annotate[1]
+
+AUTHOR
+------
+Written by Fredrik Kuivinen <freku045@student.liu.se>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
new file mode 100644 (file)
index 0000000..d43ef1d
--- /dev/null
@@ -0,0 +1,109 @@
+git-branch(1)
+=============
+
+NAME
+----
+git-branch - List, create, or delete branches.
+
+SYNOPSIS
+--------
+[verse]
+'git-branch' [-r]
+'git-branch' [-l] [-f] <branchname> [<start-point>]
+'git-branch' (-d | -D) <branchname>...
+
+DESCRIPTION
+-----------
+With no arguments given (or just `-r`) a list of available branches
+will be shown, the current branch will be highlighted with an asterisk.
+
+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 `-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.
+
+
+OPTIONS
+-------
+-d::
+       Delete a branch. The branch must be fully merged.
+
+-D::
+       Delete a branch irrespective of its index status.
+
+-l::
+       Create the branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
+-f::
+       Force the creation of a new branch even if it means deleting
+       a branch that already exists with the same name.
+
+-r::
+       List only the "remote" branches.
+
+<branchname>::
+       The name of the branch to create or delete.
+       The new branch name must pass all checks defined by
+       gitlink:git-check-ref-format[1].  Some of these checks
+       may restrict the characters allowed in a branch name.
+
+<start-point>::
+       The new branch will be created with a HEAD equal to this.  It may
+       be given as a branch name, a commit-id, or a tag.  If this option
+       is omitted, the current branch is assumed.
+
+
+
+Examples
+--------
+
+Start development off of a known tag::
++
+------------
+$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
+$ cd my2.6
+$ git branch my2.6.14 v2.6.14   <1>
+$ git checkout my2.6.14
+------------
++
+<1> This step and the next one could be combined into a single step with
+"checkout -b my2.6.14 v2.6.14".
+
+Delete unneeded branch::
++
+------------
+$ git clone git://git.kernel.org/.../git.git my.git
+$ cd my.git
+$ git branch -D todo    <1>
+------------
++
+<1> delete todo branch even if the "master" branch does not have all
+commits from todo branch.
+
+
+Notes
+-----
+
+If you are creating a branch that you want to immediately checkout, it's
+easier to use the git checkout command with its `-b` option to create
+a branch and check it out with a single command.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and 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-cat-file.txt b/Documentation/git-cat-file.txt
new file mode 100644 (file)
index 0000000..5e9cbf8
--- /dev/null
@@ -0,0 +1,72 @@
+git-cat-file(1)
+===============
+
+NAME
+----
+git-cat-file - Provide content or type information for repository objects
+
+
+SYNOPSIS
+--------
+'git-cat-file' [-t | -s | -e | -p | <type>] <object>
+
+DESCRIPTION
+-----------
+Provides content or type of objects in the repository. The type
+is required unless '-t' or '-p' is used to find the object type,
+or '-s' is used to find the object size.
+
+OPTIONS
+-------
+<object>::
+       The sha1 identifier of the object.
+
+-t::
+       Instead of the content, show the object type identified by
+       <object>.
+
+-s::
+       Instead of the content, show the object size identified by
+       <object>.
+
+-e::
+       Suppress all output; instead exit with zero status if <object>
+       exists and is a valid object.
+
+-p::
+       Pretty-print the contents of <object> based on its type.
+
+<type>::
+       Typically this matches the real type of <object> but asking
+       for a type that can trivially be dereferenced from the given
+       <object> is also permitted.  An example is to ask for a
+       "tree" with <object> being a commit object that contains it,
+       or to ask for a "blob" with <object> being a tag object that
+       points at it.
+
+OUTPUT
+------
+If '-t' is specified, one of the <type>.
+
+If '-s' is specified, the size of the <object> in bytes.
+
+If '-e' is specified, no output.
+
+If '-p' is specified, the contents of <object> are pretty-printed.
+
+Otherwise the raw (though uncompressed) contents of the <object> will
+be returned.
+
+
+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-check-ref-format.txt b/Documentation/git-check-ref-format.txt
new file mode 100644 (file)
index 0000000..3ea720d
--- /dev/null
@@ -0,0 +1,54 @@
+git-check-ref-format(1)
+=======================
+
+NAME
+----
+git-check-ref-format - Make sure ref name is well formed
+
+SYNOPSIS
+--------
+'git-check-ref-format' <refname>
+
+DESCRIPTION
+-----------
+Checks if a given 'refname' is acceptable, and exits non-zero if
+it is not.
+
+A reference is used in git to specify branches and tags.  A
+branch head is stored under `$GIT_DIR/refs/heads` directory, and
+a tag is stored under `$GIT_DIR/refs/tags` directory.  git
+imposes the following rules on how refs are named:
+
+. It could be named hierarchically (i.e. separated with slash
+  `/`), but each of its component cannot begin with a dot `.`;
+
+. It cannot have two consecutive dots `..` anywhere;
+
+. It cannot have ASCII control character (i.e. bytes whose
+  values are lower than \040, or \177 `DEL`), space, tilde `~`,
+  caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
+  or open bracket `[` anywhere;
+
+. It cannot end with a slash `/`.
+
+These rules makes it easy for shell script based tools to parse
+refnames, pathname expansion by the shell when a refname is used
+unquoted (by mistake), and also avoids ambiguities in certain
+refname expressions (see gitlink:git-rev-parse[1]).  Namely:
+
+. double-dot `..` are often used as in `ref1..ref2`, and in some
+  context this notation means `{caret}ref1 ref2` (i.e. not in
+  ref1 and in ref2).
+
+. tilde `~` and caret `{caret}` are used to introduce postfix
+  'nth parent' and 'peel onion' operation.
+
+. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
+  value and store it in dstref" in fetch and push operations.
+  It may also be used to select a specific object such as with
+  gitlink:git-cat-file[1] "git-cat-file blob v1.3.3:refs.c".
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
new file mode 100644 (file)
index 0000000..765c173
--- /dev/null
@@ -0,0 +1,185 @@
+git-checkout-index(1)
+=====================
+
+NAME
+----
+git-checkout-index - Copy files from the index to the working directory
+
+
+SYNOPSIS
+--------
+[verse]
+'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
+                  [--stage=<number>|all]
+                  [--temp]
+                  [-z] [--stdin]
+                  [--] [<file>]\*
+
+DESCRIPTION
+-----------
+Will copy all files listed from the index to the working directory
+(not overwriting existing files).
+
+OPTIONS
+-------
+-u|--index::
+       update stat information for the checked out entries in
+       the index file.
+
+-q|--quiet::
+       be quiet if files exist or are not in the index
+
+-f|--force::
+       forces overwrite of existing files
+
+-a|--all::
+       checks out all files in the index.  Cannot be used
+       together with explicit filenames.
+
+-n|--no-create::
+       Don't checkout new files, only refresh files already checked
+       out.
+
+--prefix=<string>::
+       When creating files, prepend <string> (usually a directory
+       including a trailing /)
+
+--stage=<number>|all::
+       Instead of checking out unmerged entries, copy out the
+       files from named stage.  <number> must be between 1 and 3.
+       Note: --stage=all automatically implies --temp.
+
+--temp::
+       Instead of copying the files to the working directory
+       write the content to temporary files.  The temporary name
+       associations will be written to stdout.
+
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
+
+\--::
+       Do not interpret any more arguments as options.
+
+The order of the flags used to matter, but not anymore.
+
+Just doing `git-checkout-index` does nothing. You probably meant
+`git-checkout-index -a`. And if you want to force it, you want
+`git-checkout-index -f -a`.
+
+Intuitiveness is not the goal here. Repeatability is. The reason for
+the "no arguments means no work" behavior is that from scripts you are
+supposed to be able to do:
+
+----------------
+$ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
+----------------
+
+which will force all existing `*.h` files to be replaced with their
+cached copies. If an empty command line implied "all", then this would
+force-refresh everything in the index, which was not the point.  But
+since git-checkout-index accepts --stdin it would be faster to use:
+
+----------------
+$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+----------------
+
+The `--` is just a good idea when you know the rest will be filenames;
+it will prevent problems with a filename of, for example,  `-a`.
+Using `--` is probably a good policy in scripts.
+
+
+Using --temp or --stage=all
+---------------------------
+When `--temp` is used (or implied by `--stage=all`)
+`git-checkout-index` will create a temporary file for each index
+entry being checked out.  The index will not be updated with stat
+information.  These options can be useful if the caller needs all
+stages of all unmerged entries so that the unmerged files can be
+processed by an external merge tool.
+
+A listing will be written to stdout providing the association of
+temporary file names to tracked path names.  The listing format
+has two variations:
+
+    . tempname TAB path RS
++
+The first format is what gets used when `--stage` is omitted or
+is not `--stage=all`. The field tempname is the temporary file
+name holding the file content and path is the tracked path name in
+the index.  Only the requested entries are output.
+
+    . stage1temp SP stage2temp SP stage3tmp TAB path RS
++
+The second format is what gets used when `--stage=all`.  The three
+stage temporary fields (stage1temp, stage2temp, stage3temp) list the
+name of the temporary file if there is a stage entry in the index
+or `.` if there is no stage entry.  Paths which only have a stage 0
+entry will always be omitted from the output.
+
+In both formats RS (the record separator) is newline by default
+but will be the null byte if -z was passed on the command line.
+The temporary file names are always safe strings; they will never
+contain directory separators or whitespace characters.  The path
+field is always relative to the current directory and the temporary
+file names are always relative to the top level directory.
+
+If the object being copied out to a temporary file is a symbolic
+link the content of the link will be written to a normal file.  It is
+up to the end-user or the Porcelain to make use of this information.
+
+
+EXAMPLES
+--------
+To update and refresh only the files already checked out::
++
+----------------
+$ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
+----------------
+
+Using `git-checkout-index` to "export an entire tree"::
+       The prefix ability basically makes it trivial to use
+       `git-checkout-index` as an "export as tree" function.
+       Just read the desired tree into the index, and do:
++
+----------------
+$ git-checkout-index --prefix=git-export-dir/ -a
+----------------
++
+`git-checkout-index` will "export" the index into the specified
+directory.
++
+The final "/" is important. The exported name is literally just
+prefixed with the specified string.  Contrast this with the
+following example.
+
+Export files with a prefix::
++
+----------------
+$ git-checkout-index --prefix=.merged- Makefile
+----------------
++
+This will check out the currently cached copy of `Makefile`
+into the file `.merged-Makefile`.
+
+
+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-checkout.txt b/Documentation/git-checkout.txt
new file mode 100644 (file)
index 0000000..fbdbadc
--- /dev/null
@@ -0,0 +1,156 @@
+git-checkout(1)
+===============
+
+NAME
+----
+git-checkout - Checkout and switch to a branch
+
+SYNOPSIS
+--------
+[verse]
+'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
+'git-checkout' [-m] [<branch>] <paths>...
+
+DESCRIPTION
+-----------
+
+When <paths> are not given, this command switches branches by
+updating the index and working tree to reflect the specified
+branch, <branch>, and updating HEAD to be <branch> or, if
+specified, <new_branch>.  Using -b will cause <new_branch> to
+be created.
+
+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
+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.
+
+
+OPTIONS
+-------
+-f::
+       Force a re-read of everything.
+
+-b::
+       Create a new branch named <new_branch> and start it at
+       <branch>.  The new branch name must pass all checks defined
+       by gitlink:git-check-ref-format[1].  Some of these checks
+       may restrict the characters allowed in a branch name.
+
+-l::
+       Create the new branch's ref log.  This activates recording of
+       all changes to made the branch ref, enabling use of date
+       based sha1 expressions such as "<branchname>@{yesterday}".
+
+-m::
+       If you have local modifications to one or more files that
+       are different between the current branch and the branch to
+       which you are switching, the command refuses to switch
+       branches in order to preserve your modifications in context.
+       However, with this option, a three-way merge between the current
+       branch, your working tree contents, and the new branch
+       is done, and you will be on the new branch.
++
+When a merge conflict happens, the index entries for conflicting
+paths are left unmerged, and you need to resolve the conflicts
+and mark the resolved paths with `git update-index`.
+
+<new_branch>::
+       Name for the new branch.
+
+<branch>::
+       Branch to checkout; may be any object ID that resolves to a
+       commit. Defaults to HEAD.
+
+
+EXAMPLES
+--------
+
+. The following sequence checks out the `master` branch, reverts
+the `Makefile` to two revisions back, deletes hello.c by
+mistake, and gets it back from the index.
++
+------------
+$ git checkout master             <1>
+$ git checkout master~2 Makefile  <2>
+$ rm -f hello.c
+$ git checkout hello.c            <3>
+------------
++
+<1> switch branch
+<2> take out a file out of other commit
+<3> restore hello.c from HEAD of current branch
++
+If you have an unfortunate branch that is named `hello.c`, this
+step would be confused as an instruction to switch to that branch.
+You should instead write:
++
+------------
+$ git checkout -- hello.c
+------------
+
+. After working in a wrong branch, switching to the correct
+branch would be done using:
++
+------------
+$ git checkout mytopic
+------------
++
+However, your "wrong" branch and correct "mytopic" branch may
+differ in files that you have locally modified, in which case,
+the above checkout would fail like this:
++
+------------
+$ git checkout mytopic
+fatal: Entry 'frotz' not uptodate. Cannot merge.
+------------
++
+You can give the `-m` flag to the command, which would try a
+three-way merge:
++
+------------
+$ git checkout -m mytopic
+Auto-merging frotz
+------------
++
+After this three-way merge, the local modifications are _not_
+registered in your index file, so `git diff` would show you what
+changes you made since the tip of the new branch.
+
+. When a merge conflict happens during switching branches with
+the `-m` option, you would see something like this:
++
+------------
+$ git checkout -m mytopic
+Auto-merging frotz
+merge: warning: conflicts during merge
+ERROR: Merge conflict in frotz
+fatal: merge program failed
+------------
++
+At this point, `git diff` shows the changes cleanly merged as in
+the previous example, as well as the changes in the conflicted
+files.  Edit and resolve the conflict and mark it resolved with
+`git update-index` as usual:
++
+------------
+$ edit frotz
+$ git update-index frotz
+------------
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
new file mode 100644 (file)
index 0000000..bfa950c
--- /dev/null
@@ -0,0 +1,60 @@
+git-cherry-pick(1)
+==================
+
+NAME
+----
+git-cherry-pick - Apply the change introduced by an existing commit
+
+SYNOPSIS
+--------
+'git-cherry-pick' [--edit] [-n] [-r] <commit>
+
+DESCRIPTION
+-----------
+Given one existing commit, apply the change the patch introduces, and record a
+new commit that records it.  This requires your working tree to be clean (no
+modifications from the HEAD commit).
+
+OPTIONS
+-------
+<commit>::
+       Commit to cherry-pick.
+
+-e|--edit::
+       With this option, `git-cherry-pick` will let you edit the commit
+       message prior committing.
+
+-r|--replay::
+       Usually the command appends which commit was
+       cherry-picked after the original commit message when
+       making a commit.  This option, '--replay', causes it to
+       use the original commit message intact.  This is useful
+       when you are reordering the patches in your private tree
+       before publishing.
+
+-n|--no-commit::
+       Usually the command automatically creates a commit with
+       a commit log message stating which commit was
+       cherry-picked.  This flag applies the change necessary
+       to cherry-pick the named commit to your working tree,
+       but does not make the commit.  In addition, when this
+       option is used, your working tree does not have to match
+       the HEAD commit.  The cherry-pick is done against the
+       beginning state of your working tree.
++
+This is useful when cherry-picking more than one commits'
+effect to your working tree in a row.
+
+
+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-cherry.txt b/Documentation/git-cherry.txt
new file mode 100644 (file)
index 0000000..893baaa
--- /dev/null
@@ -0,0 +1,51 @@
+git-cherry(1)
+=============
+
+NAME
+----
+git-cherry - Find commits not merged upstream
+
+SYNOPSIS
+--------
+'git-cherry' [-v] <upstream> [<head>]
+
+DESCRIPTION
+-----------
+The changeset (or "diff") of each commit between the fork-point and <head>
+is compared against each commit between the fork-point and <upstream>.
+
+Every commit with a changeset that doesn't exist in the other branch
+has its id (sha1) reported, prefixed by a symbol.  Those existing only
+in the <upstream> branch are prefixed with a minus (-) sign, and those
+that only exist in the <head> branch are prefixed with a plus (+) symbol.
+
+Because git-cherry compares the changeset rather than the commit id
+(sha1), you can use git-cherry to find out if a commit you made locally
+has been applied <upstream> under a different commit id.  For example,
+this will happen if you're feeding patches <upstream> via email rather
+than pushing or pulling commits directly.
+
+
+OPTIONS
+-------
+-v::
+       Verbose.
+
+<upstream>::
+       Upstream branch to compare against.
+
+<head>::
+       Working branch; defaults to HEAD.
+
+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-clean.txt b/Documentation/git-clean.txt
new file mode 100644 (file)
index 0000000..c61afbc
--- /dev/null
@@ -0,0 +1,53 @@
+git-clean(1)
+============
+
+NAME
+----
+git-clean - Remove untracked files from the working tree
+
+SYNOPSIS
+--------
+[verse]
+'git-clean' [-d] [-n] [-q] [-x | -X] [--] <paths>...
+
+DESCRIPTION
+-----------
+Removes files unknown to git.  This allows to clean the working tree
+from files that are not under version control.  If the '-x' option is
+specified, ignored files are also removed, allowing to remove all
+build products.
+When optional `<paths>...` arguments are given, the paths
+affected are further limited to those that match them.
+
+
+OPTIONS
+-------
+-d::
+       Remove untracked directories in addition to untracked files.
+
+-n::
+       Don't actually remove anything, just show what would be done.
+
+-q::
+       Be quiet, only report errors, but not the files that are
+       successfully removed.
+
+-x::
+       Don't use the ignore rules.  This allows removing all untracked
+       files, including build products.  This can be used (possibly in
+       conjunction with gitlink:git-reset[1]) to create a pristine
+       working directory to test a clean build.
+
+-X::
+       Remove only files ignored by git.  This may be useful to rebuild
+       everything from scratch, but keep manually created files.
+
+
+Author
+------
+Written by Pavel Roskin <proski@gnu.org>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-clone-pack.txt b/Documentation/git-clone-pack.txt
new file mode 100644 (file)
index 0000000..09f43ee
--- /dev/null
@@ -0,0 +1,64 @@
+git-clone-pack(1)
+=================
+
+NAME
+----
+git-clone-pack - Clones a repository by receiving packed objects
+
+
+SYNOPSIS
+--------
+'git-clone-pack' [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
+
+DESCRIPTION
+-----------
+Clones a repository into the current repository by invoking
+'git-upload-pack', possibly on the remote host via ssh, in
+the named repository, and stores the sent pack in the local
+repository.
+
+OPTIONS
+-------
+--exec=<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.
+       Installations of sshd ignore the user's environment
+       setup scripts for login shells (e.g. .bash_profile) and
+       your privately installed git may not be found on the system
+       default $PATH.  Another workaround suggested is to set
+       up your $PATH in ".bashrc", but this flag is for people
+       who do not want to pay the overhead for non-interactive
+       shells by having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+<head>...::
+       The heads to update.  This is relative to $GIT_DIR
+       (e.g. "HEAD", "refs/heads/master").  When unspecified,
+       all heads are updated to match the remote repository.
++
+Usually all the refs from existing repository are stored
+under the same name in the new repository.  Giving explicit
+<head> arguments instead writes the object names and refs to
+the standard output, just like get-fetch-pack does.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
new file mode 100644 (file)
index 0000000..94d9393
--- /dev/null
@@ -0,0 +1,167 @@
+git-clone(1)
+============
+
+NAME
+----
+git-clone - Clones a repository
+
+
+SYNOPSIS
+--------
+[verse]
+'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
+         [-o <name>] [-u <upload-pack>] [--reference <repository>]
+         <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:
+
+       Pull: master:origin
+
+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.
+
+
+OPTIONS
+-------
+--local::
+-l::
+       When the repository to clone from is on a local machine,
+       this flag bypasses normal "git aware" transport
+       mechanism and clones the repository by making a copy of
+       HEAD and everything under objects and refs directories.
+       The files under .git/objects/ directory are hardlinked
+       to save space when possible.
+
+--shared::
+-s::
+       When the repository to clone is on the local machine,
+       instead of using hard links, automatically setup
+       .git/objects/info/alternates to share the objects
+       with the source repository.  The resulting repository
+       starts out without any object of its own.
+
+--reference <repository>::
+       If the reference repository is on the local machine
+       automatically setup .git/objects/info/alternates to
+       obtain objects from the reference repository.  Using
+       an already existing repository as an alternate will
+       require less objects to be copied from the repository
+       being cloned, reducing network and local storage costs.
+
+--quiet::
+-q::
+       Operate quietly.  This flag is passed to "rsync" and
+       "git-clone-pack" commands when given.
+
+-n::
+       No checkout of HEAD is performed after the clone is complete.
+
+--bare::
+       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.
+
+-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.
+
+--upload-pack <upload-pack>::
+-u <upload-pack>::
+       When given, and the repository to clone from is handled
+       by 'git-clone-pack', '--exec=<upload-pack>' is passed to
+       the command to specify non-default path for the command
+       run on the other end.
+
+--template=<template_directory>::
+       Specify the directory from which templates will be used;
+       if unset the templates are taken from the installation
+       defined default, typically `/usr/share/git-core/templates`.
+
+<repository>::
+       The (possibly remote) repository to clone from.  It can
+       be any URL git-fetch supports.
+
+<directory>::
+       The name of a new directory to clone into.  The "humanish"
+       part of the source repository is used if no directory is
+       explicitly given ("repo" for "/path/to/repo.git" and "foo"
+       for "host.xz:foo/.git").  Cloning into an existing directory
+       is not allowed.
+
+Examples
+--------
+
+Clone from upstream::
++
+------------
+$ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
+$ cd my2.6
+$ make
+------------
+
+
+Make a local clone that borrows from the current directory, without checking things out::
++
+------------
+$ git clone -l -s -n . ../copy
+$ cd copy
+$ git show-branch
+------------
+
+
+Clone from upstream while borrowing from an existing local directory::
++
+------------
+$ git clone --reference my2.6 \
+       git://git.kernel.org/pub/scm/.../linux-2.7 \
+       my2.7
+$ cd my2.7
+------------
+
+
+Create a bare repository to publish your changes to the public::
++
+------------
+$ git clone --bare -l /home/proj/.git /pub/scm/proj.git
+------------
+
+
+Create a repository on the kernel.org machine that borrows from Linus::
++
+------------
+$ git clone --bare -l -s /pub/scm/.../torvalds/linux-2.6.git \
+    /pub/scm/.../me/subsys-2.6.git
+------------
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
new file mode 100644 (file)
index 0000000..41d1a1c
--- /dev/null
@@ -0,0 +1,100 @@
+git-commit-tree(1)
+==================
+
+NAME
+----
+git-commit-tree - Creates a new commit object
+
+
+SYNOPSIS
+--------
+'git-commit-tree' <tree> [-p <parent commit>]\* < changelog
+
+DESCRIPTION
+-----------
+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.
+
+A commit object usually has 1 parent (a commit after a change) or up
+to 16 parents.  More than one parent represents a merge of branches
+that led to them.
+
+While a tree represents a particular directory state of a working
+directory, a commit represents that state in "time", and explains how
+to get there.
+
+Normally a commit would identify 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 that is pointed at by
+`.git/HEAD`, so that we can always see what the last committed
+state was.
+
+OPTIONS
+-------
+<tree>::
+       An existing tree object
+
+-p <parent commit>::
+       Each '-p' indicates the id of a parent commit object.
+       
+
+Commit Information
+------------------
+
+A commit encapsulates:
+
+- all parent object ids
+- author name, email and date
+- committer name and email and the commit time.
+
+If not provided, "git-commit-tree" uses your name, hostname and domain to
+provide author and committer info. This can be overridden by
+either `.git/config` file, or using the following environment variables.
+
+       GIT_AUTHOR_NAME
+       GIT_AUTHOR_EMAIL
+       GIT_AUTHOR_DATE
+       GIT_COMMITTER_NAME
+       GIT_COMMITTER_EMAIL
+
+(nb "<", ">" and "\n"s are stripped)
+
+In `.git/config` file, the following items are used for GIT_AUTHOR_NAME and
+GIT_AUTHOR_EMAIL:
+
+       [user]
+               name = "Your Name"
+               email = "your@email.address.xz"
+
+A commit comment is read from stdin (max 999 chars). If a changelog
+entry is not provided via "<" redirection, "git-commit-tree" will just wait
+for one to be entered and terminated with ^D.
+
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+    The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+    The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+    The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+gitlink:git-write-tree[1]
+
+
+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-commit.txt b/Documentation/git-commit.txt
new file mode 100644 (file)
index 0000000..0fe66f2
--- /dev/null
@@ -0,0 +1,170 @@
+git-commit(1)
+=============
+
+NAME
+----
+git-commit - Record your changes
+
+SYNOPSIS
+--------
+[verse]
+'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>]
+          [--no-verify] [--amend] [-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
+VISUAL and EDITOR environment variables to edit the commit log
+message.
+
+Several environment variable are used during commits.  They are
+documented in gitlink:git-commit-tree[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.
+
+-c or -C <commit>::
+       Take existing commit object, and reuse the log message
+       and the authorship information (including the timestamp)
+       when creating the commit.  With '-C', the editor is not
+       invoked; with '-c' the user can further edit the commit
+       message.
+
+-F <file>::
+       Take the commit message from the given file.  Use '-' to
+       read the message from the standard input.
+
+--author <author>::
+       Override the author name used in the commit.  Use
+       `A U Thor <author@example.com>` format.
+
+-m <msg>::
+       Use the given <msg> as the commit message.
+
+-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`.
+
+-e|--edit::
+       The message taken from file with `-F`, command line with
+       `-m`, and from file with `-C` are usually used as the
+       commit log message unmodified.  This option lets you
+       further edit the message taken from these sources.
+
+--amend::
+
+       Used to amend the tip of the current branch. Prepare the tree
+       object you would want to replace the latest commit as usual
+       (this includes the usual -i/-o and explicit paths), and the
+       commit log editor is seeded with the commit message from the
+       tip of the current branch. The commit you create replaces the
+       current tip -- if it was a merge, it will have the parents of
+       the current tip as parents -- so the current top commit is
+       discarded.
++
+--
+It is a rough equivalent for:
+------
+       $ git reset --soft HEAD^
+       $ ... do something else to come up with the right tree ...
+       $ git commit -c ORIG_HEAD
+
+------
+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.
+
+-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.
+
+\--::
+       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].
+
+
+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.
+
+`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.
+
+. 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.
+
+. 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...`.
+
+`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.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-convert-objects.txt b/Documentation/git-convert-objects.txt
new file mode 100644 (file)
index 0000000..b1220c0
--- /dev/null
@@ -0,0 +1,29 @@
+git-convert-objects(1)
+======================
+
+NAME
+----
+git-convert-objects - Converts old-style git repository
+
+
+SYNOPSIS
+--------
+'git-convert-objects'
+
+DESCRIPTION
+-----------
+Converts old-style git repository to the latest format
+
+
+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-count-objects.txt b/Documentation/git-count-objects.txt
new file mode 100644 (file)
index 0000000..198ce77
--- /dev/null
@@ -0,0 +1,38 @@
+git-count-objects(1)
+====================
+
+NAME
+----
+git-count-objects - Reports on unpacked objects
+
+SYNOPSIS
+--------
+'git-count-objects' [-v]
+
+DESCRIPTION
+-----------
+This counts the number of unpacked object files and disk space consumed by
+them, to help you decide when it is a good time to repack.
+
+
+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`.
+
+
+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-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt
new file mode 100644 (file)
index 0000000..56bd3e5
--- /dev/null
@@ -0,0 +1,83 @@
+git-cvsexportcommit(1)
+======================
+
+NAME
+----
+git-cvsexportcommit - Export a commit to a CVS checkout
+
+
+SYNOPSIS
+--------
+'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+
+
+DESCRIPTION
+-----------
+Exports a commit from GIT to a CVS checkout, making it easier
+to merge patches from a git repository into a CVS repository. 
+
+Execute it from the root of the CVS working copy. GIT_DIR must be defined. 
+See examples below.
+
+It does its best to do the safe thing, it will check that the files are 
+unchanged and up to date in the CVS checkout, and it will not autocommit 
+by default.
+
+Supports file additions, removals, and commits that affect binary files.
+
+If the commit is a merge commit, you must tell git-cvsapplycommit what parent
+should the changeset be done against. 
+
+OPTIONS
+-------
+
+-c::
+       Commit automatically if the patch applied cleanly. It will not
+       commit if any hunks fail to apply or there were other problems.
+
+-p::
+       Be pedantic (paranoid) when applying patches. Invokes patch with 
+       --fuzz=0
+
+-f::
+       Force the merge even if the files are not up to date.
+
+-m::
+       Prepend the commit message with the provided prefix. 
+       Useful for patch series and the like.
+
+-v::
+       Verbose.
+
+EXAMPLES
+--------
+
+Merge one patch into CVS::
++
+------------
+$ export GIT_DIR=~/project/.git
+$ cd ~/project_cvs_checkout
+$ git-cvsexportcommit -v <commit-sha1>
+$ cvs commit -F .mgs <files> 
+------------
+
+Merge pending patches into CVS automatically -- only if you really know what you are doing ::
++
+------------
+$ export GIT_DIR=~/project/.git
+$ cd ~/project_cvs_checkout
+$ git-cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git-cvsexportcommit -c -p -v
+------------
+
+Author
+------
+Written by Martin Langhoff <martin@catalyst.net.nz>
+
+Documentation
+--------------
+Documentation by Martin Langhoff <martin@catalyst.net.nz>
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
new file mode 100644 (file)
index 0000000..b0c6d7c
--- /dev/null
@@ -0,0 +1,141 @@
+git-cvsimport(1)
+================
+
+NAME
+----
+git-cvsimport - Import a CVS repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+'git-cvsimport' [-o <branch-for-HEAD>] [-h] [-v] [-d <CVSROOT>] [-s <subst>]
+             [-p <options-for-cvsps>] [-C <git_repository>] [-i] [-P <file>]
+             [-m] [-M regex] [<CVS_module>]
+
+
+DESCRIPTION
+-----------
+Imports a CVS repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+Splitting the CVS log into patch sets is done by 'cvsps'.
+At least version 2.1 is required.
+
+You should *never* do any work of your own on the branches that are
+created by git-cvsimport. The initial import will create and populate a
+"master" branch from the CVS repository's main branch which you're free
+to work with; after that, you need to 'git merge' incremental imports, or
+any CVS branches, yourself.
+
+OPTIONS
+-------
+-d <CVSROOT>::
+       The root of the CVS archive. May be local (a simple path) or remote;
+       currently, only the :local:, :ext: and :pserver: access methods 
+       are supported.
+
+-C <target-dir>::
+        The git repository to import to.  If the directory doesn't
+        exist, it will be created.  Default is the current directory.
+
+-i::
+       Import-only: don't perform a checkout after importing.  This option
+       ensures the working directory and index remain untouched and will
+       not create them if they do not exist.
+
+-k::
+       Kill keywords: will extract files with -kk from the CVS archive
+       to avoid noisy changesets. Highly recommended, but off by default
+       to preserve compatibility with early imported trees. 
+
+-u::
+       Convert underscores in tag and branch names to dots.
+
+-o <branch-for-HEAD>::
+       The 'HEAD' branch from CVS is imported to the 'origin' branch within
+       the git repository, as 'HEAD' already has a special meaning for git.
+       Use this option if you want to import into a different branch.
++
+Use '-o master' for continuing an import that was initially done by
+the old cvs2git tool.
+
+-p <options-for-cvsps>::
+       Additional options for cvsps.
+       The options '-u' and '-A' are implicit and should not be used here.
++
+If you need to pass multiple options, separate them with a comma.
+
+-P <cvsps-output-file>::
+       Instead of calling cvsps, read the provided cvsps output file. Useful
+       for debugging or when cvsps is being handled outside cvsimport.
+
+-m::    
+       Attempt to detect merges based on the commit message. This option
+       will enable default regexes that try to capture the name source 
+       branch name from the commit message. 
+
+-M <regex>::
+       Attempt to detect merges based on the commit message with a custom
+       regex. It can be used with -m to also see the default regexes. 
+       You must escape forward slashes. 
+
+-v::
+       Verbosity: let 'cvsimport' report what it is doing.
+
+<CVS_module>::
+       The CVS module you want to import. Relative to <CVSROOT>.
+
+-h::
+       Print a short usage message and exit.
+
+-z <fuzz>::
+        Pass the timestamp fuzz factor to cvsps.
+
+-s <subst>::
+       Substitute the character "/" in branch names with <subst>
+
+-A <author-conv-file>::
+       CVS by default uses the unix username when writing its
+       commit logs. Using this option and an author-conv-file
+       in this format
++
+---------
+       exon=Andreas Ericsson <ae@op5.se>
+       spawn=Simon Pawn <spawn@frog-pond.org>
+
+---------
++
+git-cvsimport will make it appear as those authors had
+their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly
+all along.
++
+For convenience, this data is saved to $GIT_DIR/cvs-authors
+each time the -A option is provided and read from that same
+file each time git-cvsimport is run.
++
+It is not recommended to use this feature if you intend to
+export changes back to CVS again later with
+git-link[1]::git-cvsexportcommit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
new file mode 100644 (file)
index 0000000..952635d
--- /dev/null
@@ -0,0 +1,148 @@
+git-cvsserver(1)
+================
+
+NAME
+----
+git-cvsserver - A CVS server emulator for git
+
+SYNOPSIS
+--------
+[verse]
+export CVS_SERVER=git-cvsserver
+'cvs' -d :ext:user@server/path/repo.git co <HEAD_name>
+
+DESCRIPTION
+-----------
+
+This application is a CVS emulation layer for git.
+
+It is highly functional. However, not all methods are implemented,
+and for those methods that are implemented,
+not all switches are implemented.
+
+Testing has been done using both the CLI CVS client, and the Eclipse CVS
+plugin. Most functionality works fine with both of these clients.
+
+LIMITATIONS
+-----------
+
+Currently cvsserver works over SSH connections for read/write clients, and
+over pserver for anonymous CVS access.
+
+CVS clients cannot tag, branch or perform GIT merges.
+
+INSTALLATION
+------------
+
+1. If you are going to offer anonymous CVS access via pserver, add a line in
+   /etc/inetd.conf like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver
+
+   Note: In some cases, you need to pass the 'pserver' argument twice for
+   git-cvsserver to see it. So the line would look like
+
+   cvspserver stream tcp nowait nobody git-cvsserver pserver pserver
+
+   No special setup is needed for SSH access, other than having GIT tools
+   in the PATH. If you have clients that do not accept the CVS_SERVER
+   env variable, you can rename git-cvsserver to cvs.
+
+2. For each repo that you want accessible from CVS you need to edit config in
+   the repo and add the following section.
+
+   [gitcvs]
+        enabled=1
+        # optional for debugging
+        logfile=/path/to/logfile
+
+   Note: you need to ensure each user that is going to invoke git-cvsserver has
+   write access to the log file and to the git repository. When offering anon
+   access via pserver, this means that the nobody user should have write access
+   to at least the sqlite database at the root of the repository.
+
+3. On the client machine you need to set the following variables.
+   CVSROOT should be set as per normal, but the directory should point at the
+   appropriate git repo. For example:
+
+   For SSH access, CVS_SERVER should be set to git-cvsserver
+
+   Example:
+
+     export CVSROOT=:ext:user@server:/var/git/project.git
+     export CVS_SERVER=git-cvsserver
+
+4. For SSH clients that will make commits, make sure their .bashrc file
+   sets the GIT_AUTHOR and GIT_COMMITTER variables.
+
+5. Clients should now be able to check out the project. Use the CVS 'module'
+   name to indicate what GIT 'head' you want to check out. Example:
+
+     cvs co -d project-master master
+
+Eclipse CVS Client Notes
+------------------------
+
+To get a checkout with the Eclipse CVS client:
+
+1. Select "Create a new project -> From CVS checkout"
+2. Create a new location. See the notes below for details on how to choose the
+   right protocol.
+3. Browse the 'modules' available. It will give you a list of the heads in
+   the repository. You will not be able to browse the tree from there. Only
+   the heads.
+4. Pick 'HEAD' when it asks what branch/tag to check out. Untick the
+   "launch commit wizard" to avoid committing the .project file.
+
+Protocol notes: If you are using anonymous access via pserver, just select that.
+Those using SSH access should choose the 'ext' protocol, and configure 'ext'
+access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to
+'git-cvsserver'. Not that password support is not good when using 'ext',
+you will definitely want to have SSH keys setup.
+
+Alternatively, you can just use the non-standard extssh protocol that Eclipse
+offer. In that case CVS_SERVER is ignored, and you will have to replace
+the cvs utility on the server with git-cvsserver or manipulate your .bashrc
+so that calling 'cvs' effectively calls git-cvsserver.
+
+Clients known to work
+---------------------
+
+CVS 1.12.9 on Debian
+CVS 1.11.17 on MacOSX (from Fink package)
+Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes)
+TortoiseCVS
+
+Operations supported
+--------------------
+
+All the operations required for normal use are supported, including
+checkout, diff, status, update, log, add, remove, commit.
+Legacy monitoring operations are not supported (edit, watch and related).
+Exports and tagging (tags and branches) are not supported at this stage.
+
+The server will set the -k mode to binary when relevant. In proper GIT
+tradition, the contents of the files are always respected.
+No keyword expansion or newline munging is supported.
+
+Dependencies
+------------
+
+git-cvsserver depends on DBD::SQLite.
+
+Copyright and Authors
+---------------------
+
+This program is copyright The Open University UK - 2006.
+
+Authors: Martyn Smith    <martyn@catalyst.net.nz>
+         Martin Langhoff <martin@catalyst.net.nz>
+         with ideas and patches from participants of the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz> Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
new file mode 100644 (file)
index 0000000..4c357da
--- /dev/null
@@ -0,0 +1,102 @@
+git-daemon(1)
+=============
+
+NAME
+----
+git-daemon - A really simple server for git repositories
+
+SYNOPSIS
+--------
+[verse]
+'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+             [--timeout=n] [--init-timeout=n] [--strict-paths]
+             [--base-path=path] [--user-path | --user-path=path]
+            [directory...]
+
+DESCRIPTION
+-----------
+A really simple TCP git daemon that normally listens on port "DEFAULT_GIT_PORT"
+aka 9418. It waits for a connection, and will just execute "git-upload-pack"
+when it gets one.
+
+It's careful in that there's a magic request-line that gives the command and
+what directory to upload, and it verifies that the directory is OK.
+
+It verifies that the directory has the magic file "git-daemon-export-ok", and
+it will refuse to export any git directory that hasn't explicitly been marked
+for export this way (unless the '--export-all' parameter is specified). If you
+pass some directory paths as 'git-daemon' arguments, you can further restrict
+the offers to a whitelist comprising of those.
+
+This is ideally suited for read-only updates, i.e., pulling from git repositories.
+
+OPTIONS
+-------
+--strict-paths::
+       Match paths exactly (i.e. don't allow "/foo/repo" when the real path is
+       "/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths.
+       git-daemon will refuse to start when this option is enabled and no
+       whitelist is specified.
+
+--base-path::
+       Remap all the path requests as relative to the given path.
+       This is sort of "GIT root" - if you run git-daemon with
+       '--base-path=/srv/git' on example.com, then if you later try to pull
+       'git://example.com/hello.git', `git-daemon` will interpret the path
+       as '/srv/git/hello.git'.
+
+--export-all::
+       Allow pulling from all directories that look like GIT repositories
+       (have the 'objects' and 'refs' subdirectories), even if they
+       do not have the 'git-daemon-export-ok' file.
+
+--inetd::
+       Have the server run as an inetd service. Implies --syslog.
+
+--port::
+       Listen on an alternative port.
+
+--init-timeout::
+       Timeout between the moment the connection is established and the
+       client request is received (typically a rather low value, since
+       that should be basically immediate).
+
+--timeout::
+       Timeout for specific client sub-requests. This includes the time
+       it takes for the server to process the sub-request and time spent
+       waiting for next client's request.
+
+--syslog::
+       Log to syslog instead of stderr. Note that this option does not imply
+       --verbose, thus by default only error conditions will be logged.
+
+--user-path, --user-path=path::
+       Allow ~user notation to be used in requests.  When
+       specified with no parameter, requests to
+       git://host/~alice/foo is taken as a request to access
+       'foo' repository in the home directory of user `alice`.
+       If `--user-path=path` is specified, the same request is
+       taken as a request to access `path/foo` repository in
+       the home directory of user `alice`.
+
+--verbose::
+       Log details about the incoming connections and requested files.
+
+<directory>::
+       A directory to add to the whitelist of allowed directories. Unless
+       --strict-paths is specified this will also include subdirectories
+       of each named directory.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>, YOSHIFUJI Hideaki
+<yoshfuji@linux-ipv6.org> and the git-list <git@vger.kernel.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
new file mode 100644 (file)
index 0000000..7a253ea
--- /dev/null
@@ -0,0 +1,79 @@
+git-describe(1)
+===============
+
+NAME
+----
+git-describe - Show the most recent tag that is reachable from a commit
+
+
+SYNOPSIS
+--------
+'git-describe' [--all] [--tags] [--abbrev=<n>] <committish>...
+
+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.
+
+
+OPTIONS
+-------
+<committish>::
+       The object name of the comittish. 
+
+--all::
+       Instead of using only the annotated tags, use any ref
+       found in `.git/refs/`.
+
+--tags::
+       Instead of using only the annotated tags, use any tag
+       found in `.git/refs/tags`.
+
+--abbrev=<n>::
+       Instead of using the default 8 hexadecimal digits as the
+       abbreviated object name, use <n> digits.
+
+
+EXAMPLES
+--------
+
+With something like git.git current tree, I get:
+
+       [torvalds@g5 git]$ git-describe parent
+       v1.0.4-g2414721b
+
+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`.
+
+Doing a "git-describe" on a tag-name will just show the tag name:
+
+       [torvalds@g5 git]$ git-describe v1.0.4
+       v1.0.4
+
+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
+
+       [torvalds@g5 git]$ git describe --all HEAD^
+       heads/lt/describe-g975b
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>, but somewhat
+butchered by Junio C Hamano <junkio@cox.net>
+
+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-diff-files.txt b/Documentation/git-diff-files.txt
new file mode 100644 (file)
index 0000000..481b8b3
--- /dev/null
@@ -0,0 +1,58 @@
+git-diff-files(1)
+=================
+
+NAME
+----
+git-diff-files - Compares files in the working tree and the index
+
+
+SYNOPSIS
+--------
+'git-diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...]
+
+DESCRIPTION
+-----------
+Compares the files in the working tree and the index.  When paths
+are specified, compares only those named paths.  Otherwise all
+entries in the index are compared.  The output format is the
+same as "git-diff-index" and "git-diff-tree".
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+-1 -2 -3 or --base --ours --theirs, and -0::
+       Diff against the "base" version, "our branch" or "their
+       branch" respectively.  With these options, diffs for
+       merged entries are not shown.
++
+The default is to diff against our branch (-2) and the 
+cleanly resolved paths.  The option -0 can be given to
+omit diff output for unmerged entries and just show "Unmerged".
+
+-c,--cc::
+       This compares stage 2 (our branch), stage 3 (their
+       branch) and the working tree file and outputs a combined
+       diff, similar to the way 'diff-tree' shows a merge
+       commit with these flags.
+
+-q::
+       Remain silent even on nonexisting files
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+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-diff-index.txt b/Documentation/git-diff-index.txt
new file mode 100644 (file)
index 0000000..9cd43f1
--- /dev/null
@@ -0,0 +1,133 @@
+git-diff-index(1)
+=================
+
+NAME
+----
+git-diff-index - Compares content and mode of blobs between the index and repository
+
+
+SYNOPSIS
+--------
+'git-diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs found via a tree
+object with the content of the current index and, optionally
+ignoring the stat state of the file on disk.  When paths are
+specified, compares only those named paths.  Otherwise all
+entries in the index are compared.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+       The id of a tree object to diff against.
+
+--cached::
+       do not consider the on-disk file at all
+
+-m::
+       By default, files recorded in the index but not checked
+       out are reported as deleted.  This flag makes
+       "git-diff-index" say that all non-checked-out files are up
+       to date.
+
+Output format
+-------------
+include::diff-format.txt[]
+
+Operating Modes
+---------------
+You can choose whether you want to trust the index file entirely
+(using the '--cached' flag) or ask the diff logic to show any files
+that don't match the stat state as being "tentatively changed".  Both
+of these operations are very useful indeed.
+
+Cached Mode
+-----------
+If '--cached' is specified, it allows you to ask:
+
+       show me the differences between HEAD and the current index
+       contents (the ones I'd write with a "git-write-tree")
+
+For example, let's say that you have worked on your working directory, updated
+some files in the index and are ready to commit. You want to see exactly
+*what* you are going to commit is without having to write a new tree
+object and compare it that way, and to do that, you just do
+
+       git-diff-index --cached HEAD
+
+Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had
+done an "git-update-index" to make that effective in the index file.
+"git-diff-files" wouldn't show anything at all, since the index file
+matches my working directory. But doing a "git-diff-index" does:
+
+  torvalds@ppc970:~/git> git-diff-index --cached HEAD
+  -100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        commit.c
+  +100644 blob    4161aecc6700a2eb579e842af0b7f22b98443f74        git-commit.c
+
+You can trivially see that the above is a rename.
+
+In fact, "git-diff-index --cached" *should* always be entirely equivalent to
+actually doing a "git-write-tree" and comparing that. Except this one is much
+nicer for the case where you just want to check where you are.
+
+So doing a "git-diff-index --cached" is basically very useful when you are
+asking yourself "what have I already marked for being committed, and 
+what's the difference to a previous tree".
+
+Non-cached Mode
+---------------
+The "non-cached" mode takes a different approach, and is potentially
+the more useful of the two in that what it does can't be emulated with
+a "git-write-tree" + "git-diff-tree". Thus that's the default mode.
+The non-cached version asks the question:
+
+  show me the differences between HEAD and the currently checked out
+  tree - index contents _and_ files that aren't up-to-date
+
+which is obviously a very useful question too, since that tells you what
+you *could* commit. Again, the output matches the "git-diff-tree -r"
+output to a tee, but with a twist.
+
+The twist is that if some file doesn't match the index, we don't have
+a backing store thing for it, and we use the magic "all-zero" sha1 to
+show that. So let's say that you have edited `kernel/sched.c`, but
+have not actually done a "git-update-index" on it yet - there is no
+"object" associated with the new state, and you get:
+
+  torvalds@ppc970:~/v2.6/linux> git-diff-index HEAD
+  *100644->100664 blob    7476bb......->000000......      kernel/sched.c
+
+i.e., it shows that the tree has changed, and that `kernel/sched.c` has is
+not up-to-date and may contain new stuff. The all-zero sha1 means that to
+get the real diff, you need to look at the object in the working directory
+directly rather than do an object-to-object diff.
+
+NOTE: As with other commands of this type, "git-diff-index" does not
+actually look at the contents of the file at all. So maybe
+`kernel/sched.c` hasn't actually changed, and it's just that you
+touched it. In either case, it's a note that you need to
+"git-update-index" it to make the index be in sync.
+
+NOTE: You can have a mixture of files show up as "has been updated"
+and "is still dirty in the working directory" together. You can always
+tell which file is in which state, since the "has been updated" ones
+show a valid sha1, and the "not in sync with the index" ones will
+always have the special all-zero sha1.
+
+
+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-diff-stages.txt b/Documentation/git-diff-stages.txt
new file mode 100644 (file)
index 0000000..3273918
--- /dev/null
@@ -0,0 +1,40 @@
+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
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
new file mode 100644 (file)
index 0000000..f7e8ff2
--- /dev/null
@@ -0,0 +1,169 @@
+git-diff-tree(1)
+================
+
+NAME
+----
+git-diff-tree - Compares the content and mode of blobs found via two tree objects
+
+
+SYNOPSIS
+--------
+[verse]
+'git-diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
+             [-t] [-r] [-c | --cc] [--root] [<common diff options>]
+             <tree-ish> [<tree-ish>] [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs found via two tree objects.
+
+If there is only one <tree-ish> given, the commit is compared with its parents
+(see --stdin below).
+
+Note that "git-diff-tree" can use the tree encapsulated in a commit object.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+       The id of a tree object.
+
+<path>...::
+       If provided, the results are limited to a subset of files
+       matching one of these prefix strings.
+       i.e., file matches `/^<pattern1>|<pattern2>|.../`
+       Note that this parameter does not provide any wildcard or regexp
+       features.
+
+-r::
+        recurse into sub-trees
+
+-t::
+       show tree entry itself as well as subtrees.  Implies -r.
+
+--root::
+       When '--root' is specified the initial commit will be showed as a big
+       creation event. This is equivalent to a diff against the NULL tree.
+
+--stdin::
+       When '--stdin' is specified, the command does not take
+       <tree-ish> arguments from the command line.  Instead, it
+       reads either one <commit> or a pair of <tree-ish>
+       separated with a single space from its standard input.
++
+When a single commit is given on one line of such input, it compares
+the commit with its parents.  The following flags further affects its
+behavior.  This does not apply to the case where two <tree-ish>
+separated with a single space are given.
+
+-m::
+       By default, "git-diff-tree --stdin" does not show
+       differences for merge commits.  With this flag, it shows
+       differences to that commit from all of its parents. See
+       also '-c'.
+
+-s::
+       By default, "git-diff-tree --stdin" shows differences,
+       either in machine-readable form (without '-p') or in patch
+       form (with '-p').  This output can be suppressed.  It is
+       only useful with '-v' flag.
+
+-v::
+       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.
+
+--no-commit-id::
+       git-diff-tree outputs a line with the commit ID when
+       applicable.  This flag suppressed the commit ID output.
+
+-c::
+       This flag changes the way a merge commit is displayed
+       (which means it is useful only when the command is given
+       one <tree-ish>, or '--stdin').  It shows the differences
+       from each of the parents to the merge result simultaneously
+       instead of showing pairwise diff between a parent and the
+       result one at a time (which is what the '-m' option does).
+       Furthermore, it lists only files which were modified
+       from all parents.
+
+--cc::
+       This flag changes the way a merge commit patch is displayed,
+       in a similar way to the '-c' option. It implies the '-c'
+       and '-p' options and further compresses the patch output
+       by omitting hunks that show differences from only one
+       parent, or show the same change from all but one parent
+       for an Octopus merge.  When this optimization makes all
+       hunks disappear, the commit itself and the commit log
+       message is not shown, just like in any other "empty diff" case.
+
+--always::
+       Show the commit itself and the commit log message even
+       if the diff itself is empty.
+
+
+Limiting Output
+---------------
+If you're only interested in differences in a subset of files, for
+example some architecture-specific files, you might do:
+
+       git-diff-tree -r <tree-ish> <tree-ish> arch/ia64 include/asm-ia64
+
+and it will only show you what changed in those two directories.
+
+Or if you are searching for what changed in just `kernel/sched.c`, just do
+
+       git-diff-tree -r <tree-ish> <tree-ish> kernel/sched.c
+
+and it will ignore all differences to other files.
+
+The pattern is always the prefix, and is matched exactly.  There are no
+wildcards.  Even stricter, it has to match a complete path component.
+I.e. "foo" does not pick up `foobar.h`.  "foo" does match `foo/bar.h`
+so it can be used to name subdirectories.
+
+An example of normal usage is:
+
+  torvalds@ppc970:~/git> git-diff-tree 5319e4......
+  *100664->100664 blob    ac348b.......->a01513.......      git-fsck-objects.c
+
+which tells you that the last commit changed just one file (it's from
+this one:
+
+-----------------------------------------------------------------------------
+commit 3c6f7ca19ad4043e9e72fa94106f352897e651a8
+tree 5319e4d609cdd282069cc4dce33c1db559539b03
+parent b4e628ea30d5ab3606119d2ea5caeab141d38df7
+author Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+committer Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+
+Make "git-fsck-objects" print out all the root commits it finds.
+
+Once I do the reference tracking, I'll also make it print out all the
+HEAD commits it finds, which is even more interesting.
+-----------------------------------------------------------------------------
+
+in case you care).
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+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-diff.txt b/Documentation/git-diff.txt
new file mode 100644 (file)
index 0000000..7ab2080
--- /dev/null
@@ -0,0 +1,116 @@
+git-diff(1)
+===========
+
+NAME
+----
+git-diff - Show changes between commits, commit and working tree, etc
+
+
+SYNOPSIS
+--------
+'git-diff' [ --diff-options ] <ent>{0,2} [<path>...]
+
+DESCRIPTION
+-----------
+Show changes between two ents, an ent and the working tree, an
+ent 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 ents given to the command.
+
+* When no <ent> is given, the working tree and the index
+  file is compared, using `git-diff-files`.
+
+* When one <ent> is given, the working tree and the named
+  tree is compared, using `git-diff-index`.  The option
+  `--cached` can be given to compare the index file and
+  the named tree.
+
+* When two <ent>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.
+
+<path>...::
+       The <path> arguments are also passed to `git-diff-\*`
+       commands.
+
+
+EXAMPLES
+--------
+
+Various ways to check your working tree::
++
+------------
+$ git diff            <1>
+$ git diff --cached   <2>
+$ git diff HEAD       <3>
+------------
++
+<1> changes in the working tree since your last git-update-index.
+<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
+would be committing if you run "git commit -a"
+
+Comparing with arbitrary commits::
++
+------------
+$ git diff test            <1>
+$ git diff HEAD -- ./test  <2>
+$ git diff HEAD^ HEAD      <3>
+------------
++
+<1> instead of using the tip of the current branch, compare with the
+tip of "test" branch.
+<2> instead of comparing with the tip of "test" branch, compare with
+the tip of the current branch, but limit the comparison to the
+file "test".
+<3> compare the version before the last commit and the last commit.
+
+
+Limiting the diff output::
++
+------------
+$ git diff --diff-filter=MRC            <1>
+$ git diff --name-status -r             <2>
+$ git diff arch/i386 include/asm-i386   <3>
+------------
++
+<1> show only modification, rename and copy, but not addition
+nor deletion.
+<2> show only names and the nature of change, but not actual
+diff output.  --name-status disables usual patch generation
+which in turn also disables recursive behavior, so without -r
+you would only see the directory name if there is a change in a
+file in a subdirectory.
+<3> limit diff output to named subtrees.
+
+Munging the diff output::
++
+------------
+$ git diff --find-copies-harder -B -C  <1>
+$ git diff -R                          <2>
+------------
++
+<1> spend extra cycles to find renames, copies and complete
+rewrites (very expensive).
+<2> output diff in reverse.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
new file mode 100644 (file)
index 0000000..bff9aa6
--- /dev/null
@@ -0,0 +1,73 @@
+git-fetch-pack(1)
+=================
+
+NAME
+----
+git-fetch-pack - Receive missing objects from another repository
+
+
+SYNOPSIS
+--------
+'git-fetch-pack' [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
+
+DESCRIPTION
+-----------
+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
+is found out by scanning local $GIT_DIR/refs/ and sent to
+'git-upload-pack' running on the other end.
+
+This command degenerates to download everything to complete the
+asked refs from the remote side when the local side does not
+have a common ancestor commit.
+
+
+OPTIONS
+-------
+-q::
+       Pass '-q' flag to 'git-unpack-objects'; this makes the
+       cloning process less verbose.
+
+-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.
+
+--exec=<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
+       setup scripts for login shells (e.g. .bash_profile) and
+       your privately installed git may not be found on the system
+       default $PATH.  Another workaround suggested is to set
+       up your $PATH in ".bashrc", but this flag is for people
+       who do not want to pay the overhead for non-interactive
+       shells by having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+<refs>...::
+       The remote heads to update from. This is relative to
+       $GIT_DIR (e.g. "HEAD", "refs/heads/master").  When
+       unspecified, update from all heads the remote side has.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
new file mode 100644 (file)
index 0000000..a9e86fd
--- /dev/null
@@ -0,0 +1,48 @@
+git-fetch(1)
+============
+
+NAME
+----
+git-fetch - Download objects and a head from another repository
+
+
+SYNOPSIS
+--------
+'git-fetch' <options> <repository> <refspec>...
+
+
+DESCRIPTION
+-----------
+Fetches named heads or tags from another repository, along with
+the objects necessary to complete them.
+
+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".
+
+
+OPTIONS
+-------
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+include::urls.txt[]
+
+SEE ALSO
+--------
+gitlink:git-pull[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+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-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
new file mode 100644 (file)
index 0000000..a70eb39
--- /dev/null
@@ -0,0 +1,39 @@
+git-fmt-merge-msg(1)
+====================
+
+NAME
+----
+git-fmt-merge-msg - Produce a merge commit message
+
+
+SYNOPSIS
+--------
+'git-fmt-merge-msg' <$GIT_DIR/FETCH_HEAD
+
+DESCRIPTION
+-----------
+Takes the list of merged objects on stdin and produces a suitable
+commit message to be used for the merge commit, usually to be
+passed as the '<merge-message>' argument of `git-merge`.
+
+This script is intended mostly for internal use by scripts
+automatically invoking `git-merge`.
+
+
+SEE ALSO
+--------
+gitlink:git-merge[1]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
new file mode 100644 (file)
index 0000000..4ca0014
--- /dev/null
@@ -0,0 +1,115 @@
+git-format-patch(1)
+===================
+
+NAME
+----
+git-format-patch - Prepare patches for e-mail submission
+
+
+SYNOPSIS
+--------
+[verse]
+'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach]
+                  [-s | --signoff] [--diff-options] [--start-number <n>]
+                  <since>[..<until>]
+
+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.
+
+The output of this command is convenient for e-mail submission or
+for use with gitlink:git-am[1].
+
+Each output file is numbered sequentially from 1, and uses the
+first line of the commit message (massaged for pathname safety) as
+the filename. The names of the output files are printed to standard
+output, unless the --stdout option is specified.
+
+If -o is specified, output files are created in <dir>.  Otherwise
+they are created in the current working directory.
+
+If -n is specified, instead of "[PATCH] Subject", the first line
+is formatted as "[PATCH n/m] Subject".
+
+
+OPTIONS
+-------
+-o|--output-directory <dir>::
+       Use <dir> to store the resulting files, instead of the
+       current working directory.
+
+-n|--numbered::
+       Name output in '[PATCH n/m]' format.
+
+--start-number <n>::
+       Start numbering the patches at <n> instead of 1.
+
+-k|--keep-subject::
+       Do not strip/add '[PATCH]' from the first line of the
+       commit log message.
+
+-s|--signoff::
+       Add `Signed-off-by:` line to the commit message, using
+       the committer identity of yourself.
+
+--stdout::
+       Print all commits to the standard output in mbox format,
+       instead of creating a file for each one.
+
+--attach::
+       Create attachments instead of inlining patches.
+
+
+CONFIGURATION
+-------------
+You can specify extra mail header lines to be added to each
+message in the repository configuration as follows:
+
+[format]
+        headers = "Organization: git-foo\n"
+
+
+EXAMPLES
+--------
+
+git-format-patch -k --stdout R1..R2 | git-am -3 -k::
+       Extract commits between revisions R1 and R2, and apply
+       them on top of the current branch using `git-am` to
+       cherry-pick them.
+
+git-format-patch origin::
+       Extract all commits which are in the current branch but
+       not in the origin branch.  For each commit a separate file
+       is created in the current directory.
+
+git-format-patch -M -B origin::
+       The same as the previous one.  Additionally, it detects
+       and handles renames and complete rewrites intelligently to
+       produce a renaming patch.  A renaming patch reduces the
+       amount of text output, and generally makes it easier to
+       review it.  Note that the "patch" program does not
+       understand renaming patches, so use it only when you know
+       the recipient uses git to apply your patch.
+
+
+See Also
+--------
+gitlink:git-am[1], gitlink:git-send-email[1]
+
+
+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-fsck-objects.txt b/Documentation/git-fsck-objects.txt
new file mode 100644 (file)
index 0000000..d0af99d
--- /dev/null
@@ -0,0 +1,139 @@
+git-fsck-objects(1)
+===================
+
+NAME
+----
+git-fsck-objects - Verifies the connectivity and validity of the objects in the database
+
+
+SYNOPSIS
+--------
+[verse]
+'git-fsck-objects' [--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-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
+
diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt
new file mode 100644 (file)
index 0000000..48805b6
--- /dev/null
@@ -0,0 +1,37 @@
+git-get-tar-commit-id(1)
+========================
+
+NAME
+----
+git-get-tar-commit-id - Extract commit ID from an archive created using git-tar-tree
+
+
+SYNOPSIS
+--------
+'git-get-tar-commit-id' < <tarfile>
+
+
+DESCRIPTION
+-----------
+Acts as a filter, extracting the commit ID stored in archives created by
+git-tar-tree.  It reads only the first 1024 bytes of input, thus its
+runtime is not influenced by the size of <tarfile> very much.
+
+If no commit ID is found, git-get-tar-commit-id quietly exists with a
+return code of 1.  This can happen if <tarfile> had not been created
+using git-tar-tree or if the first parameter of git-tar-tree had been
+a tree ID instead of a commit ID or tag.
+
+
+Author
+------
+Written by Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
+
+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-grep.txt b/Documentation/git-grep.txt
new file mode 100644 (file)
index 0000000..7b810df
--- /dev/null
@@ -0,0 +1,95 @@
+git-grep(1)
+===========
+
+NAME
+----
+git-grep - Print lines matching a pattern
+
+
+SYNOPSIS
+--------
+[verse]
+'git-grep' [--cached]
+          [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp]
+          [-v | --invert-match]
+          [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
+          [-n] [-l | --files-with-matches] [-L | --files-without-match]
+          [-c | --count]
+          [-A <post-context>] [-B <pre-context>] [-C <context>]
+          [-f <file>] [-e <pattern>]
+          [<tree>...]
+          [--] [<path>...]
+
+DESCRIPTION
+-----------
+Look for specified patterns in the working tree files, blobs
+registered in the index file, or given tree objects.
+
+
+OPTIONS
+-------
+--cached::
+       Instead of searching in the working tree files, check
+       the blobs registered in the index file.
+
+-a | --text::
+       Process binary files as if they were text.
+
+-i | --ignore-case::
+       Ignore case differences between the patterns and the
+       files.
+
+-w | --word-regexp::
+       Match the pattern only at word boundary (either begin at the
+       beginning of a line, or preceded by a non-word character; end at
+       the end of a line or followed by a non-word character).
+
+-v | --invert-match::
+       Select non-matching lines.
+
+-E | --extended-regexp | -G | --basic-regexp::
+       Use POSIX extended/basic regexp for patterns.  Default
+       is to use basic regexp.
+
+-n::
+       Prefix the line number to matching lines.
+
+-l | --files-with-matches | -L | --files-without-match::
+       Instead of showing every matched line, show only the
+       names of files that contain (or do not contain) matches.
+
+-c | --count::
+       Instead of showing every matched line, show the number of
+       lines that match.
+
+-[ABC] <context>::
+       Show `context` trailing (`A` -- after), or leading (`B`
+       -- before), or both (`C` -- context) lines, and place a
+       line containing `--` between contiguous groups of
+       matches.
+
+-f <file>::
+       Read patterns from <file>, one per line.
+
+`<tree>...`::
+       Search blobs in the trees for specified patterns.
+
+`--`::
+       Signals the end of options; the rest of the parameters
+       are <path> limiters.
+
+
+Author
+------
+Originally written by Linus Torvalds <torvalds@osdl.org>, later
+revamped by Junio C Hamano.
+
+
+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-hash-object.txt b/Documentation/git-hash-object.txt
new file mode 100644 (file)
index 0000000..04e8d00
--- /dev/null
@@ -0,0 +1,46 @@
+git-hash-object(1)
+==================
+
+NAME
+----
+git-hash-object - Computes object ID and optionally creates a blob from a file
+
+
+SYNOPSIS
+--------
+'git-hash-object' [-t <type>] [-w] [--stdin] [--] <file>...
+
+DESCRIPTION
+-----------
+Computes the object ID value for an object with specified type
+with the contents of the named file (which can be outside of the
+work tree), and optionally writes the resulting object into the
+object database.  Reports its object ID to its standard output.
+This is used by "git-cvsimport" to update the index
+without modifying files in the work tree.  When <type> is not
+specified, it defaults to "blob". 
+
+OPTIONS
+-------
+
+-t <type>::
+       Specify the type (default: "blob").
+
+-w::
+       Actually write the object into the object database.
+
+--stdin::
+       Read the object from standard input instead of from a file.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+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-http-fetch.txt b/Documentation/git-http-fetch.txt
new file mode 100644 (file)
index 0000000..bc1a132
--- /dev/null
@@ -0,0 +1,47 @@
+git-http-fetch(1)
+=================
+
+NAME
+----
+git-http-fetch - downloads a remote git repository via HTTP
+
+
+SYNOPSIS
+--------
+'git-http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] <commit> <url>
+
+DESCRIPTION
+-----------
+Downloads a remote git repository via HTTP.
+
+OPTIONS
+-------
+commit-id::
+        Either the hash or the filename under [URL]/refs/ to
+        pull.
+
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+
+-w <filename>::
+        Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+        the local end after the transfer is complete.
+
+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-http-push.txt b/Documentation/git-http-push.txt
new file mode 100644 (file)
index 0000000..7e1f894
--- /dev/null
@@ -0,0 +1,89 @@
+git-http-push(1)
+================
+
+NAME
+----
+git-http-push - Push missing objects using HTTP/DAV
+
+
+SYNOPSIS
+--------
+'git-http-push' [--complete] [--force] [--verbose] <url> <ref> [<ref>...]
+
+DESCRIPTION
+-----------
+Sends missing objects to remote repository, and updates the
+remote branch.
+
+
+OPTIONS
+-------
+--complete::
+       Do not assume that the remote repository is complete in its
+       current state, and verify all objects in the entire local
+       ref's history exist in the remote repository.
+
+--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::
+       Report the list of objects being walked locally and the
+       list of objects successfully sent to the remote repository.
+
+<ref>...:
+       The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+A '<ref>' specification can be either a single pattern, or a pair
+of such patterns separated by a colon ":" (this means that a ref name
+cannot have a colon in it).  A single pattern '<name>' is just a 
+shorthand for '<name>:<name>'.
+
+Each pattern pair consists of the source side (before the colon)
+and the destination side (after the colon).  The ref to be
+pushed is determined by finding a match that matches the source
+side, and where it is pushed is determined by using the
+destination side.
+
+ - It is an error if <src> does not match exactly one of the
+   local refs.
+
+ - If <dst> does not match any remote ref, either
+
+   * it has to start with "refs/"; <dst> is used as the
+     destination literally in this case.
+
+   * <src> == <dst> and the ref that matched the <src> must not
+     exist in the set of remote refs; the ref matched <src>
+     locally is used as the name of the destination.
+
+Without '--force', the <src> ref is stored at the remote only if
+<dst> does not exist, or <dst> is a proper subset (i.e. an
+ancestor) of <src>.  This check, known as "fast forward check",
+is performed in order to avoid accidentally overwriting the
+remote ref and lose other peoples' commits from there.
+
+With '--force', the fast forward check is disabled for all refs.
+
+Optionally, a <ref> parameter can be prefixed with a plus '+' sign
+to disable the fast-forward check only on that ref.
+
+
+Author
+------
+Written by Nick Hengeveld <nickh@reactrix.com>
+
+Documentation
+--------------
+Documentation by Nick Hengeveld
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt
new file mode 100644 (file)
index 0000000..eca9e9c
--- /dev/null
@@ -0,0 +1,62 @@
+git-imap-send(1)
+================
+
+NAME
+----
+git-imap-send - Dump a mailbox from stdin into an imap folder
+
+
+SYNOPSIS
+--------
+'git-imap-send'
+
+
+DESCRIPTION
+-----------
+This command uploads a mailbox generated with git-format-patch
+into an imap drafts folder.  This allows patches to be sent as
+other email is sent with mail clients that cannot read mailbox
+files directly.
+
+Typical usage is something like:
+
+git-format-patch --signoff --stdout --attach origin | git-imap-send
+
+
+CONFIGURATION
+-------------
+
+git-imap-send requires the following values in the repository
+configuration file (shown with examples):
+
+..........................
+[imap]
+    Folder = "INBOX.Drafts"
+
+[imap]
+    Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null"
+
+[imap]
+    Host = imap.server.com
+    User = bob
+    Pass = pwd
+    Port = 143
+..........................
+
+
+BUGS
+----
+Doesn't handle lines starting with "From " in the message body.
+
+
+Author
+------
+Derived from isync 1.0.1 by Mike McCormack.
+
+Documentation
+--------------
+Documentation by Mike McCormack
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
new file mode 100644 (file)
index 0000000..71ce557
--- /dev/null
@@ -0,0 +1,44 @@
+git-index-pack(1)
+=================
+
+NAME
+----
+git-index-pack - Build pack index file for an existing packed archive
+
+
+SYNOPSIS
+--------
+'git-index-pack' [-o <index-file>] <pack-file>
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the specified file, and
+builds a pack index file (.idx) for it.  The packed archive
+together with the pack index can then be placed in the
+objects/pack/ directory of a git repository.
+
+
+OPTIONS
+-------
+-o <index-file>::
+       Write the generated pack index into the specified
+       file.  Without this option the name of pack index
+       file is constructed from the name of packed archive
+       file by replacing .pack with .idx (and the program
+       fails if the name of packed archive does not end
+       with .pack).
+
+
+Author
+------
+Written by Sergey Vlasov <vsu@altlinux.ru>
+
+Documentation
+-------------
+Documentation by Sergey Vlasov
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
new file mode 100644 (file)
index 0000000..8a150d8
--- /dev/null
@@ -0,0 +1,82 @@
+git-init-db(1)
+==============
+
+NAME
+----
+git-init-db - Creates an empty git repository
+
+
+SYNOPSIS
+--------
+'git-init-db' [--template=<template_directory>] [--shared]
+
+
+OPTIONS
+-------
+--template=<template_directory>::
+       Provide the directory from which templates will be used.
+       The default template directory is `/usr/share/git-core/templates`.
+
+--shared::
+       Specify that the git repository is to be shared amongst several users.
+
+
+DESCRIPTION
+-----------
+This command creates an empty git repository - basically a `.git` directory
+with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
+templated files.
+An initial `HEAD` file that references the HEAD of the master branch
+is also created.
+
+If `--template=<template_directory>` is 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.
+
+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.
+
+A shared repository allows users belonging to the same group to push into that
+repository. When specifying `--shared` the config variable "core.sharedRepository" 
+is set to 'true' so that directories under `$GIT_DIR` are made group writable
+(and g+sx, since the git group may be not the primary group of all users).
+
+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
+
diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt
new file mode 100644 (file)
index 0000000..87abec1
--- /dev/null
@@ -0,0 +1,43 @@
+git-local-fetch(1)
+==================
+
+NAME
+----
+git-local-fetch - Duplicates another git repository on a local system
+
+
+SYNOPSIS
+--------
+'git-local-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path
+
+DESCRIPTION
+-----------
+Duplicates another git repository on a local system.
+
+OPTIONS
+-------
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+
+-w <filename>::
+        Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+        the local end after the transfer is complete.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+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-log.txt b/Documentation/git-log.txt
new file mode 100644 (file)
index 0000000..c9ffff7
--- /dev/null
@@ -0,0 +1,77 @@
+git-log(1)
+==========
+
+NAME
+----
+git-log - Show commit logs
+
+
+SYNOPSIS
+--------
+'git-log' <option>...
+
+DESCRIPTION
+-----------
+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
+each commit introduces are shown.
+
+This manual page describes only the most frequently used options.
+
+
+OPTIONS
+-------
+--pretty=<format>::
+       Controls the way the commit log is formatted.
+
+--max-count=<n>::
+       Limits the number of commits to show.
+
+<since>..<until>::
+       Show only commits between the named two commits.
+
+-p::
+       Show the change the commit introduces in a patch form.
+
+<paths>...::
+       Show only commits that affect the specified paths.
+
+
+Examples
+--------
+git log --no-merges::
+
+       Show the whole commit history, but skip any merges
+
+git log v2.6.12.. include/scsi drivers/scsi::
+
+       Show all commits since version 'v2.6.12' that changed any file
+       in the include/scsi or drivers/scsi subdirectories
+
+git log --since="2 weeks ago" \-- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
+git log -r --name-status release..test::
+
+       Show the commits that are in the "test" branch but not yet
+       in the "release" branch, along with the list of paths
+       each commit modifies.
+
+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-lost-found.txt b/Documentation/git-lost-found.txt
new file mode 100644 (file)
index 0000000..f52a9d7
--- /dev/null
@@ -0,0 +1,78 @@
+git-lost-found(1)
+=================
+
+NAME
+----
+git-lost-found - Recover lost refs that luckily have not yet been pruned
+
+SYNOPSIS
+--------
+'git-lost-found'
+
+DESCRIPTION
+-----------
+Finds dangling commits and tags from the object database, and
+creates refs to them in .git/lost-found/ directory.  Commits and
+tags that dereference to commits go to .git/lost-found/commit
+and others are stored in .git/lost-found/other directory.
+
+
+OUTPUT
+------
+One line description from the commit and tag found along with
+their object name are printed on the standard output.
+
+
+EXAMPLE
+-------
+
+Suppose you run 'git tag -f' and mistyped the tag to overwrite.
+The ref to your tag is overwritten, but until you run 'git
+prune', it is still there.
+
+------------
+$ git lost-found
+[1ef2b196d909eed523d4f3c9bf54b78cdd6843c6] GIT 0.99.9c
+...
+------------
+
+Also you can use gitk to browse how they relate to each other
+and existing (probably old) tags.
+
+------------
+$ gitk $(cd .git/lost-found/commit && echo ??*)
+------------
+
+After making sure that it is the object you are looking for, you
+can reconnect it to your regular .git/refs hierarchy.
+
+------------
+$ git cat-file -t 1ef2b196
+tag
+$ git cat-file tag 1ef2b196
+object fa41bbce8e38c67a218415de6cfa510c7e50032a
+type commit
+tag v0.99.9c
+tagger Junio C Hamano <junkio@cox.net> 1131059594 -0800
+
+GIT 0.99.9c
+
+This contains the following changes from the "master" branch, since
+...
+$ git update-ref refs/tags/not-lost-anymore 1ef2b196
+$ git rev-parse not-lost-anymore
+1ef2b196d909eed523d4f3c9bf54b78cdd6843c6
+------------
+
+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-ls-files.txt b/Documentation/git-ls-files.txt
new file mode 100644 (file)
index 0000000..a29c633
--- /dev/null
@@ -0,0 +1,242 @@
+git-ls-files(1)
+===============
+
+NAME
+----
+git-ls-files - Information about files in the index/working directory
+
+
+SYNOPSIS
+--------
+[verse]
+'git-ls-files' [-z] [-t] [-v]
+               (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
+               (-[c|d|o|i|s|u|k|m])\*
+               [-x <pattern>|--exclude=<pattern>]
+               [-X <file>|--exclude-from=<file>]
+               [--exclude-per-directory=<file>]
+               [--error-unmatch]
+               [--full-name] [--abbrev] [--] [<file>]\*
+
+DESCRIPTION
+-----------
+This merges the file listing in the directory cache index with the
+actual working directory list, and shows different combinations of the
+two.
+
+One or more of the options below may be used to determine the files
+shown:
+
+OPTIONS
+-------
+-c|--cached::
+       Show cached files in the output (default)
+
+-d|--deleted::
+       Show deleted files in the output
+
+-m|--modified::
+       Show modified files in the output
+
+-o|--others::
+       Show other files in the output
+
+-i|--ignored::
+       Show ignored files in the output
+       Note the this also reverses any exclude list present.
+
+-s|--stage::
+       Show stage files in the output
+
+--directory::
+       If a whole directory is classified as "other", show just its
+       name (with a trailing slash) and not its whole contents.
+
+--no-empty-directory::
+       Do not list empty directories. Has no effect without --directory.
+
+-u|--unmerged::
+       Show unmerged files in the output (forces --stage)
+
+-k|--killed::
+       Show files on the filesystem that need to be removed due
+       to file/directory conflicts for checkout-index to
+       succeed.
+
+-z::
+       \0 line termination on output.
+
+-x|--exclude=<pattern>::
+       Skips files matching pattern.
+       Note that pattern is a shell wildcard pattern.
+
+-X|--exclude-from=<file>::
+       exclude patterns are read from <file>; 1 per line.
+
+--exclude-per-directory=<file>::
+       read additional exclude patterns that apply only to the
+       directory and its subdirectories in <file>.
+
+--error-unmatch::
+       If any <file> does not appear in the index, treat this as an
+       error (return 1).
+
+-t::
+       Identify the file status with the following tags (followed by
+       a space) at the start of each line:
+       H::     cached
+       M::     unmerged
+       R::     removed/deleted
+       C::     modified/changed
+       K::     to be killed
+       ?::     other
+
+-v::
+       Similar to `-t`, but use lowercase letters for files
+       that are marked as 'always matching index'.
+
+--full-name::
+       When run from a subdirectory, the command usually
+       outputs paths relative to the current directory.  This
+       option forces paths to be output relative to the project
+       top directory.
+
+--abbrev[=<n>]::
+       Instead of showing the full 40-byte hexadecimal object
+       lines, show only handful hexdigits prefix.
+       Non default number of digits can be specified with --abbrev=<n>.
+
+\--::
+       Do not interpret any more arguments as options.
+
+<file>::
+       Files to show. If no files are given all files which match the other
+       specified criteria are shown.
+
+Output
+------
+show files just outputs the filename unless '--stage' is specified in
+which case it outputs:
+
+        [<tag> ]<mode> <object> <stage> <file>
+
+"git-ls-files --unmerged" and "git-ls-files --stage" can be used to examine
+detailed information on unmerged paths.
+
+For an unmerged path, instead of recording a single mode/SHA1 pair,
+the dircache records up to three such pairs; one from tree O in stage
+1, A in stage 2, and B in stage 3.  This information can be used by
+the user (or the porcelain) to see what should eventually be recorded at the
+path. (see git-read-tree for more information on state)
+
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
+Exclude Patterns
+----------------
+
+'git-ls-files' can use a list of "exclude patterns" when
+traversing the directory tree and finding files to show when the
+flags --others or --ignored are specified.
+
+These exclude patterns come from these places:
+
+  1. command line flag --exclude=<pattern> specifies a single
+     pattern.
+
+  2. command line flag --exclude-from=<file> specifies a list of
+     patterns stored in a file.
+
+  3. command line flag --exclude-per-directory=<name> specifies
+     a name of the file in each directory 'git-ls-files'
+     examines, and if exists, its contents are used as an
+     additional list of patterns.
+
+An exclude pattern file used by (2) and (3) contains one pattern
+per line.  A line that starts with a '#' can be used as comment
+for readability.
+
+There are three lists of patterns that are in effect at a given
+time.  They are built and ordered in the following way:
+
+ * --exclude=<pattern> from the command line; patterns are
+   ordered in the same order as they appear on the command line.
+
+ * lines read from --exclude-from=<file>; patterns are ordered
+   in the same order as they appear in the file.
+
+ * When --exclude-per-directory=<name> is specified, upon
+   entering a directory that has such a file, its contents are
+   appended at the end of the current "list of patterns".  They
+   are popped off when leaving the directory.
+
+Each pattern in the pattern list specifies "a match pattern" and
+optionally the fate; either a file that matches the pattern is
+considered excluded or included.  A filename is matched against
+the patterns in the three lists; the --exclude-from list is
+checked first, then the --exclude-per-directory list, and then
+finally the --exclude list. The last match determines its fate.
+If there is no match in the three lists, the fate is "included".
+
+A pattern specified on the command line with --exclude or read
+from the file specified with --exclude-from is relative to the
+top of the directory tree.  A pattern read from a file specified
+by --exclude-per-directory is relative to the directory that the
+pattern file appears in.
+
+An exclude pattern is of the following format:
+
+ - an optional prefix '!' which means that the fate this pattern
+   specifies is "include", not the usual "exclude"; the
+   remainder of the pattern string is interpreted according to
+   the following rules.
+
+ - if it does not contain a slash '/', it is a shell glob
+   pattern and used to match against the filename without
+   leading directories (i.e. the same way as the current
+   implementation).
+
+ - otherwise, it is a shell glob pattern, suitable for
+   consumption by fnmatch(3) with FNM_PATHNAME flag.  I.e. a
+   slash in the pattern must match a slash in the pathname.
+   "Documentation/\*.html" matches "Documentation/git.html" but
+   not "ppc/ppc.html".  As a natural exception, "/*.c" matches
+   "cat-file.c" but not "mozilla-sha1/sha1.c".
+
+An example:
+
+--------------------------------------------------------------
+    $ cat .git/ignore
+    # ignore objects and archives, anywhere in the tree.
+    *.[oa]
+    $ cat Documentation/.gitignore
+    # ignore generated html files,
+    *.html
+    # except foo.html which is maintained by hand
+    !foo.html
+    $ git-ls-files --ignored \
+        --exclude='Documentation/*.[0-9]' \
+        --exclude-from=.git/ignore \
+        --exclude-per-directory=.gitignore
+--------------------------------------------------------------
+
+
+See Also
+--------
+gitlink:git-read-tree[1]
+
+
+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-ls-remote.txt b/Documentation/git-ls-remote.txt
new file mode 100644 (file)
index 0000000..ae4c1a2
--- /dev/null
@@ -0,0 +1,63 @@
+git-ls-remote(1)
+================
+
+NAME
+----
+git-ls-remote - Look at references other repository has
+
+
+SYNOPSIS
+--------
+'git-ls-remote' [--heads] [--tags] <repository> <refs>...
+
+DESCRIPTION
+-----------
+Displays the references other repository has.
+
+
+OPTIONS
+-------
+-h|--heads, -t|--tags::
+       Limit to only refs/heads and refs/tags, respectively.
+       These options are _not_ mutually exclusive; when given
+       both, references stored in refs/heads and refs/tags are
+       displayed.
+
+<repository>::
+       Location of the repository.  The shorthand defined in
+       $GIT_DIR/branches/ can be used.
+
+<refs>...::
+       When unspecified, all references, after filtering done
+       with --heads and --tags, are shown.  When <refs>... are
+       specified, only references matching the given patterns
+       are displayed.
+
+EXAMPLES
+--------
+
+       $ git ls-remote --tags ./.
+       d6602ec5194c87b0fc87103ca4d67251c76f233a        refs/tags/v0.99
+       f25a265a342aed6041ab0cc484224d9ca54b6f41        refs/tags/v0.99.1
+       7ceca275d047c90c0c7d5afb13ab97efdf51bd6e        refs/tags/v0.99.3
+       c5db5456ae3b0873fc659c19fafdde22313cc441        refs/tags/v0.99.2
+       0918385dbd9656cab0d1d81ba7453d49bbc16250        refs/tags/junio-gpg-pub
+       $ git ls-remote http://www.kernel.org/pub/scm/git/git.git master pu rc
+       5fe978a5381f1fbad26a80e682ddd2a401966740        refs/heads/master
+       c781a84b5204fb294c9ccc79f8b3baceeb32c061        refs/heads/pu
+       b1d096f2926c4e37c9c0b6a7bf2119bedaa277cb        refs/heads/rc
+       $ echo http://www.kernel.org/pub/scm/git/git.git >.git/branches/public
+       $ git ls-remote --tags public v\*
+       d6602ec5194c87b0fc87103ca4d67251c76f233a        refs/tags/v0.99
+       f25a265a342aed6041ab0cc484224d9ca54b6f41        refs/tags/v0.99.1
+       c5db5456ae3b0873fc659c19fafdde22313cc441        refs/tags/v0.99.2
+       7ceca275d047c90c0c7d5afb13ab97efdf51bd6e        refs/tags/v0.99.3
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
new file mode 100644 (file)
index 0000000..018c401
--- /dev/null
@@ -0,0 +1,80 @@
+git-ls-tree(1)
+==============
+
+NAME
+----
+git-ls-tree - Lists the contents of a tree object
+
+
+SYNOPSIS
+--------
+'git-ls-tree' [-d] [-r] [-t] [-z]
+       [--name-only] [--name-status] [--full-name] [--abbrev=[<n>]]
+       <tree-ish> [paths...]
+
+DESCRIPTION
+-----------
+Lists the contents of a given tree object, like what "/bin/ls -a" does
+in the current working directory. Note that the usage is subtly different,
+though - 'paths' denote just a list of patterns to match, e.g. so specifying
+directory name (without '-r') will behave differently, and order of the
+arguments does not matter.
+
+OPTIONS
+-------
+<tree-ish>::
+       Id of a tree-ish.
+
+-d::
+       Show only the named tree entry itself, not its children.
+
+-r::
+       Recurse into sub-trees.
+
+-t::
+       Show tree entries even when going to recurse them. Has no effect
+       if '-r' was not passed. '-d' implies '-t'.
+
+-z::
+       \0 line termination on output.
+
+--name-only::
+--name-status::
+       List only filenames (instead of the "long" output), one per line.
+
+--abbrev[=<n>]::
+       Instead of showing the full 40-byte hexadecimal object
+       lines, show only handful hexdigits prefix.
+       Non default number of digits can be specified with --abbrev=<n>.
+
+paths::
+       When paths are given, show them (note that this isn't really raw
+       pathnames, but rather a list of patterns to match).  Otherwise
+       implicitly uses the root level of the tree as the sole path argument.
+
+
+Output Format
+-------------
+        <mode> SP <type> SP <object> TAB <file>
+
+When the `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
+
+
+Author
+------
+Written by Petr Baudis <pasky@suse.cz>
+Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>,
+another major rewrite by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list
+<git@vger.kernel.org>.
+
+This manual page is a stub. You can help the git documentation by expanding it.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
new file mode 100644 (file)
index 0000000..ea0a065
--- /dev/null
@@ -0,0 +1,72 @@
+git-mailinfo(1)
+===============
+
+NAME
+----
+git-mailinfo - Extracts patch from a single e-mail message
+
+
+SYNOPSIS
+--------
+'git-mailinfo' [-k] [-u | --encoding=<encoding>] <msg> <patch>
+
+
+DESCRIPTION
+-----------
+Reading a single e-mail message from the standard input, and
+writes the commit log message in <msg> file, and the patches in
+<patch> file.  The author name, e-mail and e-mail subject are
+written out to the standard output to be used by git-applypatch
+to create a commit.  It is usually not necessary to use this
+command directly.
+
+
+OPTIONS
+-------
+-k::
+       Usually the program 'cleans up' the Subject: header line
+       to extract the title line for the commit log message,
+       among which (1) remove 'Re:' or 're:', (2) leading
+       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+       then prepends "[PATCH] ".  This flag forbids this
+       munging, and is most useful when used to read back 'git
+       format-patch --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.
+
+--encoding=<encoding>::
+       Similar to -u but if the local convention is different
+       from what is specified by i18n.commitencoding, this flag
+       can be used to override it.
+
+<msg>::
+       The commit log message extracted from e-mail, usually
+       except the title line which comes from e-mail Subject.
+
+<patch>::
+       The patch extracted from e-mail.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+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-mailsplit.txt b/Documentation/git-mailsplit.txt
new file mode 100644 (file)
index 0000000..209e36b
--- /dev/null
@@ -0,0 +1,52 @@
+git-mailsplit(1)
+================
+
+NAME
+----
+git-mailsplit - Totally braindamaged mbox splitter program
+
+SYNOPSIS
+--------
+'git-mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>...]
+
+DESCRIPTION
+-----------
+Splits a mbox file into a list of files: "0001" "0002" ..  in the specified
+directory so you can process them further from there.
+
+OPTIONS
+-------
+<mbox>::
+       Mbox file to split.  If not given, the mbox is read from
+       the standard input.
+
+<directory>::
+       Directory in which to place the individual messages.
+
+-b::
+       If any file doesn't begin with a From line, assume it is a
+       single mail message instead of signalling error.
+
+-d<prec>::
+       Instead of the default 4 digits with leading zeros,
+       different precision can be specified for the generated
+       filenames.
+
+-f<nn>::
+       Skip the first <nn> numbers, for example if -f3 is specified,
+       start the numbering with 0004.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+and 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-merge-base.txt b/Documentation/git-merge-base.txt
new file mode 100644 (file)
index 0000000..6099be2
--- /dev/null
@@ -0,0 +1,43 @@
+git-merge-base(1)
+=================
+
+NAME
+----
+git-merge-base - Finds as good a common ancestor as possible for a merge
+
+
+SYNOPSIS
+--------
+'git-merge-base' [--all] <commit> <commit>
+
+DESCRIPTION
+-----------
+
+"git-merge-base" finds as good a common ancestor as possible between
+the two commits. That is, given two commits A and B 'git-merge-base A
+B' will output a commit which is reachable from both A and B through
+the parent relationship.
+
+Given a selection of equally good common ancestors it should not be
+relied on to decide in any particular way.
+
+The "git-merge-base" algorithm is still in flux - use the source...
+
+OPTIONS
+-------
+--all::
+       Output all common ancestors for the two commits instead of
+       just one.
+
+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-merge-index.txt b/Documentation/git-merge-index.txt
new file mode 100644 (file)
index 0000000..6cd0601
--- /dev/null
@@ -0,0 +1,88 @@
+git-merge-index(1)
+==================
+
+NAME
+----
+git-merge-index - Runs a merge for files needing merging
+
+
+SYNOPSIS
+--------
+'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
+
+DESCRIPTION
+-----------
+This looks up the <file>(s) in the index and, if there are any merge
+entries, passes the SHA1 hash for those files as arguments 1, 2, 3 (empty
+argument if no file), and <file> as argument 4.  File modes for the three
+files are passed as arguments 5, 6 and 7.
+
+OPTIONS
+-------
+\--::
+       Do not interpret any more arguments as options.
+
+-a::
+       Run merge against all files in the index that need merging.
+
+-o::
+       Instead of stopping at the first failed merge, do all of them
+       in one shot - continue with merging even when previous merges
+       returned errors, and only return the error code after all the
+       merges are over.
+
+-q::
+       Do not complain about failed merge program (the merge program
+       failure usually indicates conflicts during merge). This is for
+       porcelains which might want to emit custom messages.
+
+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.
+
+A sample script called "git-merge-one-file" is included in the
+distribution.
+
+ALERT ALERT ALERT! The git "merge object order" is different from the
+RCS "merge" program merge object order. In the above ordering, the
+original is first. But the argument order to the 3-way merge program
+"merge" is to have the original in the middle. Don't ask me why.
+
+Examples:
+
+  torvalds@ppc970:~/merge-test> git-merge-index cat MM
+  This is MM from the original tree.                   # original
+  This is modified MM in the branch A.                 # merge1
+  This is modified MM in the branch B.                 # merge2
+  This is modified MM in the branch B.                 # current contents
+
+or 
+
+  torvalds@ppc970:~/merge-test> git-merge-index cat AA MM
+  cat: : No such file or directory
+  This is added AA in the branch A.
+  This is added AA in the branch B.
+  This is added AA in the branch B.
+  fatal: merge program failed
+
+where the latter example shows how "git-merge-index" will stop trying to
+merge once anything has returned an error (i.e., "cat" returned an error
+for the AA file, because it didn't exist in the original, and thus
+"git-merge-index" didn't even try to merge the MM thing).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+One-shot merge by Petr Baudis <pasky@ucw.cz>
+
+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-merge-one-file.txt b/Documentation/git-merge-one-file.txt
new file mode 100644 (file)
index 0000000..86aad37
--- /dev/null
@@ -0,0 +1,30 @@
+git-merge-one-file(1)
+=====================
+
+NAME
+----
+git-merge-one-file - The standard helper program to use with "git-merge-index"
+
+
+SYNOPSIS
+--------
+'git-merge-one-file'
+
+DESCRIPTION
+-----------
+This is the standard helper program to use with "git-merge-index"
+to resolve a merge after the trivial merge done with "git-read-tree -m".
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>,
+Junio C Hamano <junkio@cox.net> and Petr Baudis <pasky@suse.cz>.
+
+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-merge-tree.txt b/Documentation/git-merge-tree.txt
new file mode 100644 (file)
index 0000000..35fb4fb
--- /dev/null
@@ -0,0 +1,37 @@
+git-merge-tree(1)
+=================
+
+NAME
+----
+git-merge-tree - Show three-way merge without touching index
+
+
+SYNOPSIS
+--------
+'git-merge-tree' <base-tree> <branch1> <branch2>
+
+DESCRIPTION
+-----------
+Reads three treeish, and output trivial merge results and
+conflicting stages to the standard output.  This is similar to
+what three-way read-tree -m does, but instead of storing the
+results in the index, the command outputs the entries to the
+standard output.
+
+This is meant to be used by higher level scripts to compute
+merge results outside index, and stuff the results back into the
+index.  For this reason, the output from the command omits
+entries that match <branch1> tree.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
new file mode 100644 (file)
index 0000000..4ce799b
--- /dev/null
@@ -0,0 +1,158 @@
+git-merge(1)
+============
+
+NAME
+----
+git-merge - Grand Unified Merge Driver
+
+
+SYNOPSIS
+--------
+'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
+
+
+DESCRIPTION
+-----------
+This is the top-level user interface to the merge machinery
+which drives multiple merge strategy scripts.
+
+
+OPTIONS
+-------
+include::merge-options.txt[]
+
+<msg>::
+       The commit message to be used for the merge commit (in case
+       it is created). The `git-fmt-merge-msg` script can be used
+       to give a good default for automated `git-merge` invocations.
+
+<head>::
+       our branch head commit.
+
+<remote>::
+       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.
+
+include::merge-strategies.txt[]
+
+
+If you tried a merge which resulted in a complex conflicts and
+would want to start over, you can recover with
+gitlink:git-reset[1].
+
+
+HOW MERGE WORKS
+---------------
+
+A merge is always between the current `HEAD` and one or more
+remote branch heads, and the index file must exactly match the
+tree of `HEAD` commit (i.e. the contents of the last commit) when
+it happens.  In other words, `git-diff --cached HEAD` must
+report no changes.
+
+[NOTE]
+This is a bit of lie.  In certain special cases, your index are
+allowed to be different from the tree of `HEAD` commit.  The most
+notable case is when your `HEAD` commit is already ahead of what
+is being merged, in which case your index can have arbitrary
+difference from your `HEAD` commit.  Otherwise, your index entries
+are allowed have differences from your `HEAD` commit that match
+the result of trivial merge (e.g. you received the same patch
+from external source to produce the same result as what you are
+merging).  For example, if a path did not exist in the common
+ancestor and your head commit but exists in the tree you are
+merging into your repository, and if you already happen to have
+that path exactly in your index, the merge does not have to
+fail.
+
+Otherwise, merge will refuse to do any harm to your repository
+(that is, it may fetch the objects from remote, and it may even
+update the local branch used to keep track of the remote branch
+with `git pull remote rbranch:lbranch`, but your working tree,
+`.git/HEAD` pointer and index file are left intact).
+
+You may have local modifications in the working tree files.  In
+other words, `git-diff` is allowed to report changes.
+However, the merge uses your working tree as the working area,
+and in order to prevent the merge operation from losing such
+changes, it makes sure that they do not interfere with the
+merge. Those complex tables in read-tree documentation define
+what it means for a path to "interfere with the merge".  And if
+your local modifications interfere with the merge, again, it
+stops before touching anything.
+
+So in the above two "failed merge" case, you do not have to
+worry about lossage of data --- you simply were not ready to do
+a merge, so no merge happened at all.  You may want to finish
+whatever you were in the middle of doing, and retry the same
+pull after you are done and ready.
+
+When things cleanly merge, these things happen:
+
+1. the results are updated both in the index file and in your
+   working tree,
+2. index file is written out as a tree,
+3. the tree gets committed, and 
+4. the `HEAD` pointer gets advanced.
+
+Because of 2., we require that the original state of the index
+file to match exactly the current `HEAD` commit; otherwise we
+will write out your local changes already registered in your
+index file along with the merge result, which is not good.
+Because 1. involves only the paths different between your
+branch and the remote branch you are pulling from during the
+merge (which is typically a fraction of the whole tree), you can
+have local modifications in your working tree as long as they do
+not overlap with what the merge updates.
+
+When there are conflicts, these things happen:
+
+1. `HEAD` stays the same.
+
+2. Cleanly merged paths are updated both in the index file and
+   in your working tree.
+
+3. For conflicting paths, the index file records up to three
+   versions; stage1 stores the version from the common ancestor,
+   stage2 from `HEAD`, and stage3 from the remote branch (you
+   can inspect the stages with `git-ls-files -u`).  The working
+   tree files have the result of "merge" program; i.e. 3-way
+   merge result with familiar conflict markers `<<< === >>>`.
+
+4. No other changes are done.  In particular, the local
+   modifications you had before you started merge will stay the
+   same and the index entries for them stay as they were,
+   i.e. matching `HEAD`.
+
+After seeing a conflict, you can do two things:
+
+ * Decide not to merge.  The only clean-up you need are to reset
+   the index file to the `HEAD` commit to reverse 2. and to clean
+   up working tree changes made by 2. and 3.; `git-reset` can
+   be used for this.
+
+ * Resolve the conflicts.  `git-diff` would report only the
+   conflicting paths because of the above 2. and 3..  Edit the
+   working tree files into a desirable shape, `git-update-index`
+   them, to make the index file contain what the merge result
+   should be, and run `git-commit` to commit the result.
+
+
+SEE ALSO
+--------
+gitlink:git-fmt-merge-msg[1], gitlink:git-pull[1]
+
+
+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-mktag.txt b/Documentation/git-mktag.txt
new file mode 100644 (file)
index 0000000..2860a3d
--- /dev/null
@@ -0,0 +1,47 @@
+git-mktag(1)
+============
+
+NAME
+----
+git-mktag - Creates a tag object
+
+
+SYNOPSIS
+--------
+'git-mktag' < signature_file
+
+DESCRIPTION
+-----------
+Reads a tag contents on standard input and creates a tag object
+that can also be used to sign other objects.
+
+The output is the new tag's <object> identifier.
+
+Tag Format
+----------
+A tag signature file has a very simple fixed format: three lines of
+
+  object <sha1>
+  type <typename>
+  tag <tagname>
+
+followed by some 'optional' free-form signature that git itself
+doesn't care about, but that can be verified with gpg or similar.
+
+The size of the full object is artificially limited to 8kB.  (Just
+because I'm a lazy bastard, and if you can't fit a signature in that
+size, you're doing something wrong)
+
+
+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-mktree.txt b/Documentation/git-mktree.txt
new file mode 100644 (file)
index 0000000..5f9ee60
--- /dev/null
@@ -0,0 +1,35 @@
+git-mktree(1)
+=============
+
+NAME
+----
+git-mktree - Build a tree-object from ls-tree formatted text
+
+
+SYNOPSIS
+--------
+'git-mktree' [-z]
+
+DESCRIPTION
+-----------
+Reads standard input in non-recursive `ls-tree` output format,
+and creates a tree object.  The object name of the tree object
+built is written to the standard output.
+
+OPTIONS
+-------
+-z::
+       Read the NUL-terminated `ls-tree -z` output instead.
+
+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-mv.txt b/Documentation/git-mv.txt
new file mode 100644 (file)
index 0000000..207c43a
--- /dev/null
@@ -0,0 +1,54 @@
+git-mv(1)
+=========
+
+NAME
+----
+git-mv - Move or rename a file, directory or symlink
+
+
+SYNOPSIS
+--------
+'git-mv' <options>... <args>...
+
+DESCRIPTION
+-----------
+This script is used to move or rename a file, directory or symlink.
+
+ git-mv [-f] [-n] <source> <destination>
+ git-mv [-f] [-n] [-k] <source> ... <destination directory>
+
+In the first form, it renames <source>, which must exist and be either
+a file, symlink or directory, to <destination>.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+OPTIONS
+-------
+-f::
+       Force renaming or moving of a file even if the target exists
+-k::
+        Skip move or rename actions which would lead to an error
+       condition. An error happens when a source is neither existing nor
+        controlled by GIT, or when it would overwrite an existing
+        file unless '-f' is given.
+-n::
+       Do nothing; only show what would happen
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Rewritten by Ryan Anderson <ryan@michonline.com>
+Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+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-name-rev.txt b/Documentation/git-name-rev.txt
new file mode 100644 (file)
index 0000000..ffaa004
--- /dev/null
@@ -0,0 +1,67 @@
+git-name-rev(1)
+===============
+
+NAME
+----
+git-name-rev - Find symbolic names for given revs
+
+
+SYNOPSIS
+--------
+'git-name-rev' [--tags] ( --all | --stdin | <commitish>... )
+
+DESCRIPTION
+-----------
+Finds symbolic names suitable for human digestion for revisions given in any
+format parsable by git-rev-parse.
+
+
+OPTIONS
+-------
+
+--tags::
+       Do not use branch names, but only tags to name the commits
+
+--all::
+       List all commits reachable from all refs
+
+--stdin::
+       Read from stdin, append "(<rev_name>)" to all sha1's of name'able
+       commits, and pass to stdout
+
+EXAMPLE
+-------
+
+Given a commit, find out where it is relative to the local refs. Say somebody
+wrote you about that phantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
+Of course, you look into the commit, but that only tells you what happened, but
+not the context.
+
+Enter git-name-rev:
+
+------------
+% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940
+------------
+
+Now you are wiser, because you know that it happened 940 revisions before v0.99.
+
+Another nice thing you can do is:
+
+------------
+% git log | git name-rev --stdin
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt
new file mode 100644 (file)
index 0000000..b8ff1e9
--- /dev/null
@@ -0,0 +1,165 @@
+git-p4import(1)
+===============
+
+NAME
+----
+git-p4import - Import a Perforce repository into git
+
+
+SYNOPSIS
+--------
+`git-p4import` [-q|-v] [--authors <file>] [-t <timezone>] <//p4repo/path> <branch>
+
+`git-p4import` --stitch <//p4repo/path>
+
+`git-p4import`
+
+
+DESCRIPTION
+-----------
+Import a Perforce repository into an existing git repository.  When
+a <//p4repo/path> and <branch> are specified a new branch with the
+given name will be created and the initial import will begin.
+
+Once the initial import is complete you can do an incremental import
+of new commits from the Perforce repository.  You do this by checking
+out the appropriate git branch and then running `git-p4import` without
+any options.
+
+The standard p4 client is used to communicate with the Perforce
+repository; it must be configured correctly in order for `git-p4import`
+to operate (see below).
+
+
+OPTIONS
+-------
+-q::
+       Do not display any progress information.
+
+-v::
+        Give extra progress information.
+
+\--authors::
+       Specify an authors file containing a mapping of Perforce user
+       ids to full names and email addresses (see Notes below).
+
+\--stitch::
+       Import the contents of the given perforce branch into the
+       currently checked out git branch.
+
+\--log::
+       Store debugging information in the specified file.
+
+-t::
+       Specify that the remote repository is in the specified timezone.
+       Timezone must be in the format "US/Pacific" or "Europe/London"
+       etc.  You only need to specify this once, it will be saved in
+       the git config file for the repository.
+
+<//p4repo/path>::
+       The Perforce path that will be imported into the specified branch.
+
+<branch>::
+       The new branch that will be created to hold the Perforce imports.
+
+
+P4 Client
+---------
+You must make the `p4` client command available in your $PATH and
+configure it to communicate with the target Perforce repository.
+Typically this means you must set the "$P4PORT" and "$P4CLIENT"
+environment variables.
+
+You must also configure a `p4` client "view" which maps the Perforce
+branch into the top level of your git repository, for example:
+
+------------
+Client: myhost
+
+Root:   /home/sean/import
+
+Options:   noallwrite clobber nocompress unlocked modtime rmdir
+
+View:
+        //public/jam/... //myhost/jam/...
+------------
+
+With the above `p4` client setup, you could import the "jam"
+perforce branch into a branch named "jammy", like so:
+
+------------
+$ mkdir -p /home/sean/import/jam
+$ cd /home/sean/import/jam
+$ git init-db
+$ git p4import //public/jam jammy
+------------
+
+
+Multiple Branches
+-----------------
+Note that by creating multiple "views" you can use `git-p4import`
+to import additional branches into the same git repository.
+However, the `p4` client has a limitation in that it silently
+ignores all but the last "view" that maps into the same local
+directory.  So the following will *not* work:
+
+------------
+View:
+        //public/jam/... //myhost/jam/...
+        //public/other/... //myhost/jam/...
+        //public/guest/... //myhost/jam/...
+------------
+
+If you want more than one Perforce branch to be imported into the
+same directory you must employ a workaround.  A simple option is
+to adjust your `p4` client before each import to only include a
+single view.
+
+Another option is to create multiple symlinks locally which all
+point to the same directory in your git repository and then use
+one per "view" instead of listing the actual directory.
+
+
+Tags
+----
+A git tag of the form p4/xx is created for every change imported from
+the Perforce repository where xx is the Perforce changeset number.
+Therefore after the import you can use git to access any commit by its
+Perforce number, eg. git show p4/327.
+
+The tag associated with the HEAD commit is also how `git-p4import`
+determines if their are new changes to incrementally import from the
+Perforce repository.
+
+If you import from a repository with many thousands of changes
+you will have an equal number of p4/xxxx git tags.  Git tags can
+be expensive in terms of disk space and repository operations.
+If you don't need to perform further incremental imports, you
+may delete the tags.
+
+
+Notes
+-----
+You can interrupt the import (eg. ctrl-c) at any time and restart it
+without worry.
+
+Author information is automatically determined by querying the
+Perforce "users" table using the id associated with each change.
+However, if you want to manually supply these mappings you can do
+so with the "--authors" option.  It accepts a file containing a list
+of mappings with each line containing one mapping in the format:
+
+------------
+    perforce_id = Full Name <email@address.com>
+------------
+
+
+Author
+------
+Written by Sean Estabrooks <seanlkml@sympatico.ca>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
new file mode 100644 (file)
index 0000000..4991f88
--- /dev/null
@@ -0,0 +1,112 @@
+git-pack-objects(1)
+===================
+
+NAME
+----
+git-pack-objects - Create a packed archive of objects
+
+
+SYNOPSIS
+--------
+[verse]
+'git-pack-objects' [-q] [--no-reuse-delta] [--non-empty]
+       [--local] [--incremental] [--window=N] [--depth=N]
+       {--stdout | base-name} < object-list
+
+
+DESCRIPTION
+-----------
+Reads list of objects from the standard input, and writes a packed
+archive with specified base-name, or to the standard output.
+
+A packed archive is an efficient way to transfer set of objects
+between two repositories, and also is an archival format which
+is efficient to access.  The packed archive format (.pack) is
+designed to be unpackable without having anything else, but for
+random access, accompanied with the pack index file (.idx).
+
+'git-unpack-objects' command can read the packed archive and
+expand the objects contained in the pack into "one-file
+one-object" format; this is typically done by the smart-pull
+commands when a pack is created on-the-fly for efficient network
+transport by their peers.
+
+Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
+any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES)
+enables git to read from such an archive.
+
+In a packed archive, an object is either stored as a compressed
+whole, or as a difference from some other object.  The latter is
+often called a delta.
+
+
+OPTIONS
+-------
+base-name::
+       Write into a pair of files (.pack and .idx), using
+       <base-name> to determine the name of the created file.
+       When this option is used, the two files are written in
+       <base-name>-<SHA1>.{pack,idx} files.  <SHA1> is a hash
+       of object names (currently in random order so it does
+       not have any useful meaning) to make the resulting
+       filename reasonably unique, and written to the standard
+       output of the command.
+
+--stdout::
+       Write the pack contents (what would have been written to
+       .pack file) out to the standard output.
+
+--window and --depth::
+       These two options affects how the objects contained in
+       the pack are stored using delta compression.  The
+       objects are first internally sorted by type, size and
+       optionally names and compared against the other objects
+       within --window to see if using delta compression saves
+       space.  --depth limits the maximum delta depth; making
+       it too deep affects the performance on the unpacker
+       side, because delta data needs to be applied that many
+       times to get to the necessary object.
+
+--incremental::
+       This flag causes an object already in a pack ignored
+       even if it appears in the standard input.
+
+--local::
+       This flag is similar to `--incremental`; instead of
+       ignoring all packed objects, it only ignores objects
+       that are packed and not in the local object store
+       (i.e. borrowed from an alternate).
+
+--non-empty::
+        Only create a packed archive if it would contain at
+        least one object.
+
+-q::
+       This flag makes the command not to report its progress
+       on the standard error stream.
+
+--no-reuse-delta::
+       When creating a packed archive in a repository that
+       has existing packs, the command reuses existing deltas.
+       This sometimes results in a slightly suboptimal pack.
+       This flag tells the command not to reuse existing deltas
+       but compute them from scratch.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+See Also
+--------
+gitlink:git-repack[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt
new file mode 100644 (file)
index 0000000..8fb0659
--- /dev/null
@@ -0,0 +1,58 @@
+git-pack-redundant(1)
+=====================
+
+NAME
+----
+git-pack-redundant - Program used to find redundant pack files
+
+
+SYNOPSIS
+--------
+'git-pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >
+
+DESCRIPTION
+-----------
+This program computes which packs in your repository
+are redundant. The output is suitable for piping to
+'xargs rm' if you are in the root of the repository.
+
+git-pack-redundant accepts a list of objects on standard input. Any objects
+given will be ignored when checking which packs are required. This makes the 
+following command useful when wanting to remove packs which contain unreachable
+objects.
+
+git-fsck-objects --full --unreachable | cut -d ' ' -f3 | \
+git-pack-redundant --all | xargs rm
+
+OPTIONS
+-------
+
+
+--all::
+       Processes all packs. Any filenames on the commandline are ignored.
+
+--alt-odb::
+       Don't require objects present in packs from alternate object
+       directories to be present in local packs.
+
+--verbose::
+       Outputs some statistics to stderr. Has a small performance penalty.
+
+Author
+------
+Written by Lukas Sandström <lukass@etek.chalmers.se>
+
+Documentation
+--------------
+Documentation by Lukas Sandström <lukass@etek.chalmers.se>
+
+See Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-repack[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt
new file mode 100644 (file)
index 0000000..fc27afe
--- /dev/null
@@ -0,0 +1,48 @@
+git-parse-remote(1)
+===================
+
+NAME
+----
+git-parse-remote - Routines to help parsing $GIT_DIR/remotes/
+
+
+SYNOPSIS
+--------
+'. git-parse-remote'
+
+DESCRIPTION
+-----------
+This script is included in various scripts to supply
+routines to parse files under $GIT_DIR/remotes/ and
+$GIT_DIR/branches/.
+
+The primary entry points are:
+
+get_remote_refs_for_fetch::
+       Given the list of user-supplied `<repo> <refspec>...`,
+       return the list of refs to fetch after canonicalizing
+       them into `$GIT_DIR` relative paths
+       (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/`.
+
+get_remote_refs_for_push::
+       Given the list of user-supplied `<repo> <refspec>...`,
+       return the list of refs to push in a form suitable to be
+       fed to the `git-send-pack` command.  When `<refspec>...`
+       is empty the returned list of refs consists of the
+       defaults for the given `<repo>`, if specified in
+       `$GIT_DIR/remotes/`.
+
+Author
+------
+Written by Junio C Hamano.
+
+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-patch-id.txt b/Documentation/git-patch-id.txt
new file mode 100644 (file)
index 0000000..5389097
--- /dev/null
@@ -0,0 +1,43 @@
+git-patch-id(1)
+===============
+
+NAME
+----
+git-patch-id - Generate a patch ID
+
+SYNOPSIS
+--------
+'git-patch-id' < <patch>
+
+DESCRIPTION
+-----------
+A "patch ID" is nothing but a SHA1 of the diff associated with a patch, with
+whitespace and line numbers ignored.  As such, it's "reasonably stable", but at
+the same time also reasonably unique, i.e., two patches that have the same "patch
+ID" are almost guaranteed to be the same thing.
+
+IOW, you can use this thing to look for likely duplicate commits.
+
+When dealing with git-diff-tree output, it takes advantage of
+the fact that the patch is prefixed with the object name of the
+commit, and outputs two 40-byte hexadecimal string.  The first
+string is the patch ID, and the second string is the commit ID.
+This can be used to make a mapping from patch ID to commit ID.
+
+OPTIONS
+-------
+<patch>::
+       The diff to create the ID of.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
new file mode 100644 (file)
index 0000000..a00060c
--- /dev/null
@@ -0,0 +1,52 @@
+git-peek-remote(1)
+==================
+
+NAME
+----
+git-peek-remote - Lists the references in a remote repository
+
+
+SYNOPSIS
+--------
+'git-peek-remote' [--exec=<git-upload-pack>] [<host>:]<directory>
+
+DESCRIPTION
+-----------
+Lists the references the remote repository has, and optionally
+stores them in the local repository under the same name.
+
+OPTIONS
+-------
+--exec=<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
+       setup scripts for login shells (e.g. .bash_profile) and
+       your privately installed git may not be found on the system
+       default $PATH.  Another workaround suggested is to set
+       up your $PATH in ".bashrc", but this flag is for people
+       who do not want to pay the overhead for non-interactive
+       shells, but prefer having a lean .bashrc file (they set most of
+       the things up in .bash_profile).
+
+<host>::
+       A remote host that houses the repository.  When this
+       part is specified, 'git-upload-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to sync from.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt
new file mode 100644 (file)
index 0000000..2348826
--- /dev/null
@@ -0,0 +1,51 @@
+git-prune-packed(1)
+=====================
+
+NAME
+----
+git-prune-packed - Program used to remove the extra object files that are now
+residing in a pack file.
+
+
+SYNOPSIS
+--------
+'git-prune-packed' [-n]
+
+
+DESCRIPTION
+-----------
+This program search the `$GIT_OBJECT_DIR` for all objects that currently
+exist in a pack file as well as the independent object directories.
+
+All such extra objects are removed.
+
+A pack is a collection of objects, individually compressed, with delta
+compression applied, stored in a single file, with an associated index file.
+
+Packs are used to reduce the load on mirror systems, backup engines,
+disk storage, etc.
+
+
+OPTIONS
+-------
+-n::
+        Don't actually remove any objects, only show those that would have been
+        removed.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Ryan Anderson <ryan@michonline.com>
+
+See Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-repack[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
new file mode 100644 (file)
index 0000000..a11e303
--- /dev/null
@@ -0,0 +1,61 @@
+git-prune(1)
+============
+
+NAME
+----
+git-prune - Prunes all unreachable objects from the object database
+
+
+SYNOPSIS
+--------
+'git-prune' [-n] [--] [<head>...]
+
+DESCRIPTION
+-----------
+
+This runs `git-fsck-objects --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.
+In addition, it
+prunes the unpacked objects that are also found in packs by
+running `git prune-packed`.
+
+OPTIONS
+-------
+
+-n::
+       Do not remove anything; just report what it would
+       remove.
+
+\--::
+       Do not interpret any more arguments as options.
+
+<head>...::
+       In addition to objects
+       reachable from any of our references, keep objects
+       reachable from listed <head>s.
+
+EXAMPLE
+-------
+
+To prune objects not used by your repository nor another that
+borrows from your repository via its
+`.git/objects/info/alternates`:
+
+------------
+$ git prune $(cd ../another && $(git-rev-parse --all))
+------------
+
+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-pull.txt b/Documentation/git-pull.txt
new file mode 100644 (file)
index 0000000..51577fc
--- /dev/null
@@ -0,0 +1,132 @@
+git-pull(1)
+===========
+
+NAME
+----
+git-pull - Pull and merge from another repository
+
+
+SYNOPSIS
+--------
+'git-pull' <options> <repository> <refspec>...
+
+
+DESCRIPTION
+-----------
+Runs `git-fetch` with the given parameters, and calls `git-merge`
+to merge the retrieved head(s) into the current branch.
+
+Note that you can use `.` (current directory) as the
+<repository> to pull from the local repository -- this is useful
+when merging local branches into the current branch.
+
+
+OPTIONS
+-------
+include::merge-options.txt[]
+
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+include::urls.txt[]
+
+include::merge-strategies.txt[]
+
+EXAMPLES
+--------
+
+git pull, git pull origin::
+       Fetch the default head from the repository you cloned
+       from and merge it into your current branch.
+
+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
+       when you want to include further changes to the merge,
+       or want to write your own merge commit message.
++
+You should refrain from abusing this option to sneak substantial
+changes into a merge commit.  Small fixups like bumping
+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
+------------------------------------------------
++
+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::
++
+------------------------------------------------
+$ 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
+------------------------------------------------
++
+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.
+
+
+If you tried a pull which resulted in a complex conflicts and
+would want to start over, you can recover with
+gitlink:git-reset[1].
+
+
+SEE ALSO
+--------
+gitlink:git-fetch[1], gitlink:git-merge[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+and Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Jon Loeliger,
+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-push.txt b/Documentation/git-push.txt
new file mode 100644 (file)
index 0000000..d5b5ca1
--- /dev/null
@@ -0,0 +1,87 @@
+git-push(1)
+===========
+
+NAME
+----
+git-push - Update remote refs along with associated objects
+
+
+SYNOPSIS
+--------
+'git-push' [--all] [--tags] [--force] <repository> <refspec>...
+
+DESCRIPTION
+-----------
+
+Updates remote refs using local refs, while sending objects
+necessary to complete the given refs.
+
+You can make interesting things happen to a repository
+every time you push into it, by setting up 'hooks' there.  See
+documentation for gitlink:git-receive-pack[1].
+
+
+OPTIONS
+-------
+<repository>::
+       The "remote" repository that is destination of a push
+       operation.  See the section <<URLS,GIT URLS>> below.
+
+<refspec>::
+       The canonical format of a <refspec> parameter is
+       `+?<src>:<dst>`; that is, an optional plus `+`, followed
+       by the source ref, followed by a colon `:`, followed by
+       the destination ref.
++
+The <src> side can be an
+arbitrary "SHA1 expression" that can be used as an
+argument to `git-cat-file -t`.  E.g. `master~4` (push
+four parents before the current master head).
++
+The local ref that matches <src> is used
+to fast forward the remote ref that matches <dst>.  If
+the optional plus `+` is used, the remote ref is updated
+even if it does not result in a fast forward update.
++
+Note: If no explicit refspec is found, (that is neither
+on the command line nor in any Push line of the
+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>`.
+* A parameter <ref> without a colon is equivalent to
+  <ref>`:`<ref>, hence updates <ref> in the destination from <ref>
+  in the source.
+
+\--all::
+       Instead of naming each ref to push, specifies that all
+       refs be pushed.
+
+\--tags::
+       All refs under `$GIT_DIR/refs/tags` are pushed, in
+       addition to refspecs explicitly listed on the command
+       line.
+
+-f, \--force::
+       Usually, the command refuses to update a remote ref that is
+       not a descendent 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.
+
+include::urls.txt[]
+
+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-quiltimport.txt b/Documentation/git-quiltimport.txt
new file mode 100644 (file)
index 0000000..6e9a8c3
--- /dev/null
@@ -0,0 +1,61 @@
+git-quiltimport(1)
+================
+
+NAME
+----
+git-quiltimport - Applies a quilt patchset onto the current branch
+
+
+SYNOPSIS
+--------
+[verse]
+'git-quiltimport' [--dry-run] [--author <author>] [--patches <dir>]
+
+
+DESCRIPTION
+-----------
+Applies a quilt patchset onto the current git branch, preserving
+the patch boundaries, patch order, and patch descriptions present
+in the quilt patchset.
+
+For each patch the code attempts to extract the author from the
+patch description.  If that fails it falls back to the author
+specified with --author.  If the --author flag was not given
+the patch description is displayed and the user is asked to
+interactively enter the author of the patch.
+
+If a subject is not found in the patch description the patch name is
+preserved as the 1 line subject in the git description.
+
+OPTIONS
+-------
+--dry-run::
+       Walk through the patches in the series and warn
+       if we cannot find all of the necessary information to commit
+       a patch.  At the time of this writing only missing author
+       information is warned about.
+
+--author Author Name <Author Email>::
+       The author name and email address to use when no author
+       information can be found in the patch description.
+
+--patches <dir>::
+       The directory to find the quilt patches and the
+       quilt series file.
+
+        The default for the patch directory is patches
+       or the value of the $QUILT_PATCHES environment
+       variable.
+
+Author
+------
+Written by Eric Biederman <ebiederm@lnxi.com>
+
+Documentation
+--------------
+Documentation by Eric Biederman <ebiederm@lnxi.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
new file mode 100644 (file)
index 0000000..02c7e99
--- /dev/null
@@ -0,0 +1,323 @@
+git-read-tree(1)
+================
+
+NAME
+----
+git-read-tree - Reads tree information into the index
+
+
+SYNOPSIS
+--------
+'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+
+
+DESCRIPTION
+-----------
+Reads the tree information given by <tree-ish> into the index,
+but does not actually *update* any of the files it "caches". (see:
+gitlink:git-checkout-index[1])
+
+Optionally, it can merge a tree into the index, perform a
+fast-forward (i.e. 2-way) merge, or a 3-way merge, with the `-m`
+flag.  When used with `-m`, the `-u` flag causes it to also update
+the files in the work tree with the result of the merge.
+
+Trivial merges are done by `git-read-tree` itself.  Only conflicting paths
+will be in unmerged state when `git-read-tree` returns.
+
+OPTIONS
+-------
+-m::
+       Perform a merge, not just a read.  The command will
+       refuse to run if your index file has unmerged entries,
+       indicating that you have not finished previous merge you
+       started.
+
+--reset::
+        Same as -m, except that unmerged entries are discarded
+        instead of failing.
+
+-u::
+       After a successful merge, update the files in the work
+       tree with the result of the merge.
+
+-i::
+       Usually a merge requires the index file as well as the
+       files in the working tree are up to date with the
+       current head commit, in order not to lose local
+       changes.  This flag disables the check with the working
+       tree and is meant to be used when creating a merge of
+       trees that are not directly related to the current
+       working tree status into a temporary index file.
+
+--aggressive::
+       Usually a three-way merge by `git-read-tree` resolves
+       the merge for really trivial cases and leaves other
+       cases unresolved in the index, so that Porcelains can
+       implement different merge policies.  This flag makes the
+       command to resolve a few more cases internally:
++
+* when one side removes a path and the other side leaves the path
+  unmodified.  The resolution is to remove that path.
+* when both sides remove a path.  The resolution is to remove that path.
+* when both sides adds a path identically.  The resolution
+  is to add that path.
+
+<tree-ish#>::
+       The id of the tree object(s) to be read/merged.
+
+
+Merging
+-------
+If `-m` is specified, `git-read-tree` can perform 3 kinds of
+merge, a single tree merge if only 1 tree is given, a
+fast-forward merge with 2 trees, or a 3-way merge if 3 trees are
+provided.
+
+
+Single Tree Merge
+~~~~~~~~~~~~~~~~~
+If only 1 tree is specified, git-read-tree operates as if the user did not
+specify `-m`, except that if the original index has an entry for a
+given pathname, and the contents of the path matches with the tree
+being read, the stat info from the index is used. (In other words, the
+index's stat()s take precedence over the merged tree's).
+
+That means that if you do a `git-read-tree -m <newtree>` followed by a
+`git-checkout-index -f -u -a`, the `git-checkout-index` only checks out
+the stuff that really changed.
+
+This is used to avoid unnecessary false hits when `git-diff-files` is
+run after `git-read-tree`.
+
+
+Two Tree Merge
+~~~~~~~~~~~~~~
+
+Typically, this is invoked as `git-read-tree -m $H $M`, where $H
+is the head commit of the current repository, and $M is the head
+of a foreign tree, which is simply ahead of $H (i.e. we are in a
+fast forward situation).
+
+When two trees are specified, the user is telling git-read-tree
+the following:
+
+     1. The current index and work tree is derived from $H, but
+        the user may have local changes in them since $H;
+
+     2. The user wants to fast-forward to $M.
+
+In this case, the `git-read-tree -m $H $M` command makes sure
+that no local change is lost as the result of this "merge".
+Here are the "carry forward" rules:
+
+        I (index)           H        M        Result
+       -------------------------------------------------------
+      0 nothing             nothing  nothing  (does not happen)
+      1 nothing             nothing  exists   use M
+      2 nothing             exists   nothing  remove path from index
+      3 nothing             exists   exists   use M
+
+        clean I==H  I==M
+       ------------------
+      4 yes   N/A   N/A     nothing  nothing  keep index
+      5 no    N/A   N/A     nothing  nothing  keep index
+
+      6 yes   N/A   yes     nothing  exists   keep index
+      7 no    N/A   yes     nothing  exists   keep index
+      8 yes   N/A   no      nothing  exists   fail
+      9 no    N/A   no      nothing  exists   fail
+
+     10 yes   yes   N/A     exists   nothing  remove path from index
+     11 no    yes   N/A     exists   nothing  fail
+     12 yes   no    N/A     exists   nothing  fail
+     13 no    no    N/A     exists   nothing  fail
+
+        clean (H=M)
+       ------
+     14 yes                 exists   exists   keep index
+     15 no                  exists   exists   keep index
+
+        clean I==H  I==M (H!=M)
+       ------------------
+     16 yes   no    no      exists   exists   fail
+     17 no    no    no      exists   exists   fail
+     18 yes   no    yes     exists   exists   keep index
+     19 no    no    yes     exists   exists   keep index
+     20 yes   yes   no      exists   exists   use M
+     21 no    yes   no      exists   exists   fail
+
+In all "keep index" cases, the index entry stays as in the
+original index file.  If the entry were not up to date,
+git-read-tree keeps the copy in the work tree intact when
+operating under the -u flag.
+
+When this form of git-read-tree returns successfully, you can
+see what "local changes" you made are carried forward by running
+`git-diff-index --cached $M`.  Note that this does not
+necessarily match `git-diff-index --cached $H` would have
+produced before such a two tree merge.  This is because of cases
+18 and 19 --- if you already had the changes in $M (e.g. maybe
+you picked it up via e-mail in a patch form), `git-diff-index
+--cached $H` would have told you about the change before this
+merge, but it would not show in `git-diff-index --cached $M`
+output after two-tree merge.
+
+
+3-Way Merge
+~~~~~~~~~~~
+Each "index" entry has two bits worth of "stage" state. stage 0 is the
+normal one, and is the only one you'd see in any kind of normal use.
+
+However, when you do `git-read-tree` with three trees, the "stage"
+starts out at 1.
+
+This means that you can do
+
+----------------
+$ git-read-tree -m <tree1> <tree2> <tree3>
+----------------
+
+and you will end up with an index with all of the <tree1> entries in
+"stage1", all of the <tree2> entries in "stage2" and all of the
+<tree3> entries in "stage3".  When performing a merge of another
+branch into the current branch, we use the common ancestor tree
+as <tree1>, the current branch head as <tree2>, and the other
+branch head as <tree3>.
+
+Furthermore, `git-read-tree` has special-case logic that says: if you see
+a file that matches in all respects in the following states, it
+"collapses" back to "stage0":
+
+   - stage 2 and 3 are the same; take one or the other (it makes no
+     difference - the same work has been done on our branch in
+     stage 2 and their branch in stage 3)
+
+   - stage 1 and stage 2 are the same and stage 3 is different; take
+     stage 3 (our branch in stage 2 did not do anything since the
+     ancestor in stage 1 while their branch in stage 3 worked on
+     it)
+
+   - stage 1 and stage 3 are the same and stage 2 is different take
+     stage 2 (we did something while they did nothing)
+
+The `git-write-tree` command refuses to write a nonsensical tree, and it
+will complain about unmerged entries if it sees a single entry that is not
+stage 0.
+
+OK, this all sounds like a collection of totally nonsensical rules,
+but it's actually exactly what you want in order to do a fast
+merge. The different stages represent the "result tree" (stage 0, aka
+"merged"), the original tree (stage 1, aka "orig"), and the two trees
+you are trying to merge (stage 2 and 3 respectively).
+
+The order of stages 1, 2 and 3 (hence the order of three
+<tree-ish> command line arguments) are significant when you
+start a 3-way merge with an index file that is already
+populated.  Here is an outline of how the algorithm works:
+
+- if a file exists in identical format in all three trees, it will
+  automatically collapse to "merged" state by git-read-tree.
+
+- a file that has _any_ difference what-so-ever in the three trees
+  will stay as separate entries in the index. It's up to "porcelain
+  policy" to determine how to remove the non-0 stages, and insert a
+  merged version.
+
+- the index file saves and restores with all this information, so you
+  can merge things incrementally, but as long as it has entries in
+  stages 1/2/3 (i.e., "unmerged entries") you can't write the result. So
+  now the merge algorithm ends up being really simple:
+
+  * you walk the index in order, and ignore all entries of stage 0,
+    since they've already been done.
+
+  * if you find a "stage1", but no matching "stage2" or "stage3", you
+    know it's been removed from both trees (it only existed in the
+    original tree), and you remove that entry.
+
+  * if you find a matching "stage2" and "stage3" tree, you remove one
+    of them, and turn the other into a "stage0" entry. Remove any
+    matching "stage1" entry if it exists too.  .. all the normal
+    trivial rules ..
+
+You would normally use `git-merge-index` with supplied
+`git-merge-one-file` to do this last step.  The script updates
+the files in the working tree as it merges each path and at the
+end of a successful merge.
+
+When you start a 3-way merge with an index file that is already
+populated, it is assumed that it represents the state of the
+files in your work tree, and you can even have files with
+changes unrecorded in the index file.  It is further assumed
+that this state is "derived" from the stage 2 tree.  The 3-way
+merge refuses to run if it finds an entry in the original index
+file that does not match stage 2.
+
+This is done to prevent you from losing your work-in-progress
+changes, and mixing your random changes in an unrelated merge
+commit.  To illustrate, suppose you start from what has been
+commited last to your repository:
+
+----------------
+$ JC=`git-rev-parse --verify "HEAD^0"`
+$ git-checkout-index -f -u -a $JC
+----------------
+
+You do random edits, without running git-update-index.  And then
+you notice that the tip of your "upstream" tree has advanced
+since you pulled from him:
+
+----------------
+$ git-fetch git://.... linus
+$ LT=`cat .git/FETCH_HEAD`
+----------------
+
+Your work tree is still based on your HEAD ($JC), but you have
+some edits since.  Three-way merge makes sure that you have not
+added or modified index entries since $JC, and if you haven't,
+then does the right thing.  So with the following sequence:
+
+----------------
+$ git-read-tree -m -u `git-merge-base $JC $LT` $JC $LT
+$ git-merge-index git-merge-one-file -a
+$ echo "Merge with Linus" | \
+  git-commit-tree `git-write-tree` -p $JC -p $LT
+----------------
+
+what you would commit is a pure merge between $JC and $LT without
+your work-in-progress changes, and your work tree would be
+updated to the result of the merge.
+
+However, if you have local changes in the working tree that
+would be overwritten by this merge,`git-read-tree` will refuse
+to run to prevent your changes from being lost.
+
+In other words, there is no need to worry about what exists only
+in the working tree.  When you have local changes in a part of
+the project that is not involved in the merge, your changes do
+not interfere with the merge, and are kept intact.  When they
+*do* interfere, the merge does not even start (`git-read-tree`
+complains loudly and fails without modifying anything).  In such
+a case, you can simply continue doing what you were in the
+middle of doing, and when your working tree is ready (i.e. you
+have finished your work-in-progress), attempt the merge again.
+
+
+See Also
+--------
+gitlink:git-write-tree[1]; gitlink:git-ls-files[1]
+
+
+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-rebase.txt b/Documentation/git-rebase.txt
new file mode 100644 (file)
index 0000000..08ee4aa
--- /dev/null
@@ -0,0 +1,136 @@
+git-rebase(1)
+=============
+
+NAME
+----
+git-rebase - Rebase local commits to a new head
+
+SYNOPSIS
+--------
+'git-rebase' [--onto <newbase>] <upstream> [<branch>]
+
+'git-rebase' --continue | --skip | --abort
+
+DESCRIPTION
+-----------
+git-rebase replaces <branch> with a new branch of the same name.  When
+the --onto option is provided the new branch starts out with a HEAD equal
+to <newbase>, otherwise it is equal to <upstream>.  It then attempts to
+create a new commit for each commit from the original <branch> that does
+not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run `git rebase --continue`.  Another option is to bypass the commit
+that caused the merge failure with `git rebase --skip`.  To restore the
+original <branch> and remove the .dotest working files, use the command
+`git rebase --abort` instead.
+
+Note that if <branch> is not specified on the command line, the currently
+checked out branch is used.
+
+Assume the following history exists and the current branch is "topic":
+
+------------
+          A---B---C topic
+         /
+    D---E---F---G master
+------------
+
+From this point, the result of either of the following commands:
+
+
+    git-rebase master
+    git-rebase master topic
+
+would be:
+
+------------
+                  A'--B'--C' topic
+                 /
+    D---E---F---G master
+------------
+
+While, starting from the same point, the result of either of the following
+commands:
+
+    git-rebase --onto master~1 master
+    git-rebase --onto master~1 master topic
+
+would be:
+
+------------
+              A'--B'--C' topic
+             /
+    D---E---F---G master
+------------
+
+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
+file you edit, you need to tell git that the conflict has been resolved,
+typically this would be done with
+
+
+    git update-index <filename>
+
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the rebasing process with
+
+
+    git rebase --continue
+
+
+Alternatively, you can undo the git-rebase with
+
+
+    git rebase --abort
+
+OPTIONS
+-------
+<newbase>::
+       Starting point at which to create the new commits. If the
+       --onto option is not specified, the starting point is
+       <upstream>.
+
+<upstream>::
+       Upstream branch to compare against.
+
+<branch>::
+       Working branch; defaults to HEAD.
+
+--continue::
+       Restart the rebasing process after having resolved a merge conflict.
+
+--abort::
+       Restore the original branch and abort the rebase operation.
+
+NOTES
+-----
+When you rebase a branch, you are changing its history in a way that
+will cause problems for anyone who already has a copy of the branch
+in their repository and tries to pull updates from you.  You should
+understand the implications of using 'git rebase' on a repository that
+you share.
+
+When the git rebase command is run, it will first execute a "pre-rebase"
+hook if one exists.  You can use this hook to do sanity checks and
+reject the rebase if it isn't appropriate.  Please see the template
+pre-rebase hook script for an example.
+
+You must be in the top directory of your project to start (or continue)
+a rebase.  Upon completion, <branch> will be the current branch.
+
+Author
+------
+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-receive-pack.txt b/Documentation/git-receive-pack.txt
new file mode 100644 (file)
index 0000000..60debca
--- /dev/null
@@ -0,0 +1,99 @@
+git-receive-pack(1)
+===================
+
+NAME
+----
+git-receive-pack - Receive what is pushed into it
+
+
+SYNOPSIS
+--------
+'git-receive-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-send-pack' and updates the repository with the
+information fed from the remote end.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-send-pack' side, and the
+program pair is meant to be used to push updates to remote
+repository.  For pull operations, see 'git-fetch-pack' and
+'git-clone-pack'.
+
+The command allows for creation and fast forwarding of sha1 refs
+(heads/tags) on the remote end (strictly speaking, it is the
+local end receive-pack runs, but to the user who is sitting at
+the send-pack end, it is updating the remote.  Confused?)
+
+Before each ref is updated, if $GIT_DIR/hooks/update file exists
+and executable, it is called with three parameters:
+
+       $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+The refname parameter is relative to $GIT_DIR; e.g. for the
+master head this is "refs/heads/master".  Two sha1 are the
+object names for the refname before and after the update.  Note
+that the hook is called before the refname is updated, so either
+sha1-old is 0{40} (meaning there is no such ref yet), or it
+should match what is recorded in refname.
+
+The hook should exit with non-zero status if it wants to
+disallow updating the named ref.  Otherwise it should exit with
+zero.
+
+Using this hook, it is easy to generate mails on updates to
+the local repository. This example script sends a mail with
+the commits pushed to the repository:
+
+       #!/bin/sh
+       # mail out commit update information.
+       if expr "$2" : '0*$' >/dev/null
+       then
+               echo "Created a new ref, with the following commits:"
+               git-rev-list --pretty "$2"
+       else
+               echo "New commits:"
+               git-rev-list --pretty "$3" "^$2"
+       fi |
+       mail -s "Changes to ref $1" commit-list@mydomain
+       exit 0
+
+Another hook $GIT_DIR/hooks/post-update, if exists and
+executable, is called with the list of refs that have been
+updated.  This can be used to implement repository wide cleanup
+task if needed.  The exit code from this hook invocation is
+ignored; the only thing left for git-receive-pack to do at that
+point is to exit itself anyway.  This hook can be used, for
+example, to run "git-update-server-info" if the repository is
+packed and is served via a dumb transport.
+
+       #!/bin/sh
+       exec git-update-server-info
+
+There are other real-world examples of using update and
+post-update hooks found in the Documentation/howto directory.
+
+
+OPTIONS
+-------
+<directory>::
+       The repository to sync into.
+
+
+SEE ALSO
+--------
+gitlink:git-send-pack[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
new file mode 100644 (file)
index 0000000..aca6012
--- /dev/null
@@ -0,0 +1,37 @@
+git-relink(1)
+=============
+
+NAME
+----
+git-relink - Hardlink common objects in local repositories
+
+SYNOPSIS
+--------
+'git-relink' [--safe] <dir> <dir> [<dir>]\*
+
+DESCRIPTION
+-----------
+This will scan 2 or more object repositories and look for common objects, check
+if they are hardlinked, and replace one with a hardlink to the other if not.
+
+OPTIONS
+-------
+--safe::
+       Stops if two objects with the same hash exist but have different sizes.
+       Default is to warn and continue.
+
+<dir>::
+       Directories containing a .git/objects/ subdirectory.
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com>
+
+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-repack.txt b/Documentation/git-repack.txt
new file mode 100644 (file)
index 0000000..9516227
--- /dev/null
@@ -0,0 +1,75 @@
+git-repack(1)
+=============
+
+NAME
+----
+git-repack - Script used to pack a repository from a collection of
+objects into pack files.
+
+
+SYNOPSIS
+--------
+'git-repack' [-a] [-d] [-f] [-l] [-n] [-q]
+
+DESCRIPTION
+-----------
+
+This script is used to combine all objects that do not currently
+reside in a "pack", into a pack.
+
+A pack is a collection of objects, individually compressed, with
+delta compression applied, stored in a single file, with an
+associated index file.
+
+Packs are used to reduce the load on mirror systems, backup
+engines, disk storage, etc.
+
+OPTIONS
+-------
+
+-a::
+       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'.
+
+-d::
+       After packing, if the newly created packs make some
+       existing packs redundant, remove the redundant packs.
+       Also runs gitlink:git-prune-packed[1].
+
+-l::
+        Pass the `--local` option to `git pack-objects`, see
+        gitlink:git-pack-objects[1].
+
+-f::
+        Pass the `--no-reuse-delta` option to `git pack-objects`, see
+        gitlink:git-pack-objects[1].
+
+-q::
+        Pass the `-q` option to `git pack-objects`, see
+        gitlink:git-pack-objects[1].
+
+-n::
+        Do not update the server information with
+        `git update-server-info`.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Ryan Anderson <ryan@michonline.com>
+
+See Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
new file mode 100644 (file)
index 0000000..d5142e0
--- /dev/null
@@ -0,0 +1,188 @@
+git-repo-config(1)
+==================
+
+NAME
+----
+git-repo-config - Get and set options in .git/config
+
+
+SYNOPSIS
+--------
+[verse]
+'git-repo-config' [type] name [value [value_regex]]
+'git-repo-config' [type] --replace-all name [value [value_regex]]
+'git-repo-config' [type] --get name [value_regex]
+'git-repo-config' [type] --get-all name [value_regex]
+'git-repo-config' [type] --unset name [value_regex]
+'git-repo-config' [type] --unset-all name [value_regex]
+'git-repo-config' -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.
+
+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, or
+. you try to unset/set an option for which multiple lines match.
+
+
+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).
+
+--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.
+
+--unset::
+       Remove the line matching the key from .git/config.
+
+--unset-all::
+       Remove all matching lines from .git/config.
+
+-l, --list::
+       List all variables set in .git/config.
+
+
+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 hypothetic proxy command entries actually have a postfix to discern
+to what URL they apply. 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
+
diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt
new file mode 100644 (file)
index 0000000..478a5fd
--- /dev/null
@@ -0,0 +1,40 @@
+git-request-pull(1)
+===================
+
+NAME
+----
+git-request-pull - Generates a summary of pending changes
+
+SYNOPSIS
+--------
+'git-request-pull' <start> <url> [<end>]
+
+DESCRIPTION
+-----------
+
+Summarizes the changes between two commits to the standard output, and includes
+the given URL in the generated summary.
+
+OPTIONS
+-------
+<start>::
+       Commit to start at.
+
+<url>::
+       URL to include in the summary.
+
+<end>::
+       Commit to send at; defaults to HEAD.
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com> and 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-rerere.txt b/Documentation/git-rerere.txt
new file mode 100644 (file)
index 0000000..8b6b651
--- /dev/null
@@ -0,0 +1,177 @@
+git-rerere(1)
+=============
+
+NAME
+----
+git-rerere - Reuse recorded resolve
+
+SYNOPSIS
+--------
+'git-rerere'
+
+
+DESCRIPTION
+-----------
+
+In a workflow that employs relatively long lived topic branches,
+the developer sometimes needs to resolve the same conflict over
+and over again until the topic branches are done (either merged
+to the "release" branch, or sent out and accepted upstream).
+
+This command helps this process by recording conflicted
+automerge results and corresponding hand-resolve results on the
+initial manual merge, and later by noticing the same automerge
+results and applying the previously recorded hand resolution.
+
+[NOTE]
+You need to create `$GIT_DIR/rr-cache` directory to enable this
+command.
+
+DISCUSSION
+----------
+
+When your topic branch modifies overlapping area that your
+master branch (or upstream) touched since your topic branch
+forked from it, you may want to test it with the latest master,
+even before your topic branch is ready to be pushed upstream:
+
+------------
+              o---*---o topic
+             /
+    o---o---o---*---o---o master
+------------
+
+For such a test, you need to merge master and topic somehow.
+One way to do it is to pull master into the topic branch:
+
+------------
+       $ git checkout topic
+       $ git pull . master
+
+              o---*---o---+ topic
+             /           /
+    o---o---o---*---o---o master
+------------
+
+The commits marked with `*` touch the same area in the same
+file; you need to resolve the conflicts when creating the commit
+marked with `+`.  Then you can test the result to make sure your
+work-in-progress still works with what is in the latest master.
+
+After this test merge, there are two ways to continue your work
+on the topic.  The easiest is to build on top of the test merge
+commit `+`, and when your work in the topic branch is finally
+ready, pull the topic branch into master, and/or ask the
+upstream to pull from you.  By that time, however, the master or
+the upstream might have been advanced since the test merge `+`,
+in which case the final commit graph would look like this:
+
+------------
+       $ git checkout topic
+       $ git pull . master
+       $ ... work on both topic and master branches
+       $ git checkout master
+       $ git pull . topic
+
+              o---*---o---+---o---o topic
+             /           /         \
+    o---o---o---*---o---o---o---o---+ master
+------------
+
+When your topic branch is long-lived, however, your topic branch
+would end up having many such "Merge from master" commits on it,
+which would unnecessarily clutter the development history.
+Readers of the Linux kernel mailing list may remember that Linus
+complained about such too frequent test merges when a subsystem
+maintainer asked to pull from a branch full of "useless merges".
+
+As an alternative, to keep the topic branch clean of test
+merges, you could blow away the test merge, and keep building on
+top of the tip before the test merge:
+
+------------
+       $ git checkout topic
+       $ git pull . master
+       $ git reset --hard HEAD^ ;# rewind the test merge
+       $ ... work on both topic and master branches
+       $ git checkout master
+       $ git pull . topic
+
+              o---*---o-------o---o topic
+             /                     \
+    o---o---o---*---o---o---o---o---+ master
+------------
+
+This would leave only one merge commit when your topic branch is
+finally ready and merged into the master branch.  This merge
+would require you to resolve the conflict, introduced by the
+commits marked with `*`.  However, often this conflict is the
+same conflict you resolved when you created the test merge you
+blew away.  `git-rerere` command helps you to resolve this final
+conflicted merge using the information from your earlier hand
+resolve.
+
+Running `git-rerere` command immediately after a conflicted
+automerge records the conflicted working tree files, with the
+usual conflict markers `<<<<<<<`, `=======`, and `>>>>>>>` in
+them.  Later, after you are done resolving the conflicts,
+running `git-rerere` again records the resolved state of these
+files.  Suppose you did this when you created the test merge of
+master into the topic branch.
+
+Next time, running `git-rerere` after seeing a conflicted
+automerge, if the conflict is the same as the earlier one
+recorded, it is noticed and a three-way merge between the
+earlier conflicted automerge, the earlier manual resolution, and
+the current conflicted automerge is performed by the command.
+If this three-way merge resolves cleanly, the result is written
+out to your working tree file, so you would not have to manually
+resolve it.  Note that `git-rerere` leaves the index file alone,
+so you still need to do the final sanity checks with `git diff`
+(or `git diff -c`) and `git update-index` when you are
+satisfied.
+
+As a convenience measure, `git-merge` automatically invokes
+`git-rerere` when it exits with a failed automerge, which
+records it if it is a new conflict, or reuses the earlier hand
+resolve when it is not.  `git-commit` also invokes `git-rerere`
+when recording a merge result.  What this means is that you do
+not have to do anything special yourself (Note: you still have
+to create `$GIT_DIR/rr-cache` directory to enable this command).
+
+In our example, when you did the test merge, the manual
+resolution is recorded, and it will be reused when you do the
+actual merge later with updated master and topic branch, as long
+as the earlier resolution is still applicable.
+
+The information `git-rerere` records is also used when running
+`git-rebase`.  After blowing away the test merge and continuing
+development on the topic branch:
+
+------------
+              o---*---o-------o---o topic
+             /
+    o---o---o---*---o---o---o---o   master
+
+       $ git rebase master topic
+
+                                 o---*---o-------o---o topic
+                                /
+    o---o---o---*---o---o---o---o   master
+------------
+
+you could run `git rebase master topic`, to keep yourself
+up-to-date even before your topic is ready to be sent upstream.
+This would result in falling back to three-way merge, and it
+would conflict the same way the test merge you resolved earlier.
+`git-rerere` is run by `git rebase` to help you resolve this
+conflict.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
new file mode 100644 (file)
index 0000000..73a0ffc
--- /dev/null
@@ -0,0 +1,182 @@
+git-reset(1)
+============
+
+NAME
+----
+git-reset - Reset current HEAD to the specified state
+
+SYNOPSIS
+--------
+'git-reset' [--mixed | --soft | --hard] [<commit-ish>]
+
+DESCRIPTION
+-----------
+Sets the current head to the specified commit and optionally resets the
+index and working tree to match.
+
+This command is useful if you notice some small error in a recent
+commit (or set of commits) and want to redo that part without showing
+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.
+
+OPTIONS
+-------
+--mixed::
+       Resets the index but not the working tree (i.e., the changed files
+       are preserved but not marked for commit) and reports what has not
+       been updated. This is the default action.
+
+--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
+       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.
+
+<commit-ish>::
+       Commit to make the current HEAD.
+
+Examples
+--------
+
+Undo a commit and redo::
++
+------------
+$ git commit ...
+$ git reset --soft HEAD^      <1>
+$ edit                        <2>
+$ git commit -a -c ORIG_HEAD  <3>
+------------
++
+<1> This is most often done when you remembered what you
+just committed is incomplete, or you misspelled your commit
+message, or both.  Leaves working tree as it was before "reset".
+<2> make corrections to working tree files.
+<3> "reset" copies the old head to .git/ORIG_HEAD; redo the
+commit by starting with its log message.  If you do not need to
+edit the message further, you can give -C option instead.
+
+Undo commits permanently::
++
+------------
+$ git commit ...
+$ git reset --hard HEAD~3   <1>
+------------
++
+<1> The last three commits (HEAD, HEAD^, and HEAD~2) were bad
+and you do not want to ever see them again.  Do *not* do this if
+you have already given these commits to somebody else.
+
+Undo a commit, making it a topic branch::
++
+------------
+$ git branch topic/wip     <1>
+$ git reset --hard HEAD~3  <2>
+$ git checkout topic/wip   <3>
+------------
++
+<1> You have made some commits, but realize they were premature
+to be in the "master" branch.  You want to continue polishing
+them in a topic branch, so create "topic/wip" branch off of the
+current HEAD.
+<2> Rewind the master branch to get rid of those three commits.
+<3> Switch to "topic/wip" branch and keep working.
+
+Undo update-index::
++
+------------
+$ edit                                     <1>
+$ git-update-index frotz.c filfre.c
+$ mailx                                    <2>
+$ git reset                                <3>
+$ git pull git://info.example.com/ nitfol  <4>
+------------
++
+<1> you are happily working on something, and find the changes
+in these files are in good order.  You do not want to see them
+when you run "git diff", because you plan to work on other files
+and changes with these files are distracting.
+<2> somebody asks you to pull, and the changes sounds worthy of merging.
+<3> however, you already dirtied the index (i.e. your index does
+not match the HEAD commit).  But you know the pull you are going
+to make does not affect frotz.c nor filfre.c, so you revert the
+index changes for these two files.  Your changes in working tree
+remain there.
+<4> then you can pull and merge, leaving frotz.c and filfre.c
+changes still in the working tree.
+
+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
+$ git reset --hard                 <2>
+$ git pull . topic/branch          <3>
+Updating from 41223... to 13134...
+Fast forward
+$ git reset --hard ORIG_HEAD       <4>
+------------
++
+<1> try to update from the upstream resulted in a lot of
+conflicts; you were not ready to spend a lot of time merging
+right now, so you decide to do that later.
+<2> "pull" has not made merge commit, so "git reset --hard"
+which is a synonym for "git reset --hard HEAD" clears the mess
+from the index file and the working tree.
+<3> merge a topic branch into the current branch, which resulted
+in a fast forward.
+<4> but you decided that the topic branch is not ready for public
+consumption yet.  "pull" or "merge" always leaves the original
+tip of the current branch in ORIG_HEAD, so resetting hard to it
+brings your index file and the working tree back to that state,
+and resets the tip of the branch to that commit.
+
+Interrupted workflow::
++
+Suppose you are interrupted by an urgent fix request while you
+are in the middle of a large change.  The files in your
+working tree are not in any shape to be committed yet, but you
+need to get to the other branch for a quick bugfix.
++
+------------
+$ git checkout feature ;# you were working in "feature" branch and
+$ work work work       ;# got interrupted
+$ git commit -a -m 'snapshot WIP'                 <1>
+$ git checkout master
+$ fix fix fix
+$ git commit ;# commit with real log
+$ git checkout feature
+$ git reset --soft HEAD^ ;# go back to WIP state  <2>
+$ git reset                                       <3>
+------------
++
+<1> This commit will get blown away so a throw-away log message is OK.
+<2> This removes the 'WIP' commit from the commit history, and sets
+    your working tree to the state just before you made that snapshot.
+<3> At this point the index file still has all the WIP changes you
+    committed as 'snapshot WIP'.  This updates the index to show your
+    WIP files as uncommitted.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net> and Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-resolve.txt b/Documentation/git-resolve.txt
new file mode 100644 (file)
index 0000000..4e57c2b
--- /dev/null
@@ -0,0 +1,36 @@
+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
+
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
new file mode 100644 (file)
index 0000000..ad6d14c
--- /dev/null
@@ -0,0 +1,117 @@
+git-rev-list(1)
+===============
+
+NAME
+----
+git-rev-list - Lists commit objects in reverse chronological order
+
+
+SYNOPSIS
+--------
+[verse]
+'git-rev-list' [ \--max-count=number ]
+            [ \--max-age=timestamp ]
+            [ \--min-age=timestamp ]
+            [ \--sparse ]
+            [ \--no-merges ]
+            [ \--remove-empty ]
+            [ \--all ]
+            [ \--topo-order ]
+            [ \--parents ]
+            [ [\--objects | \--objects-edge] [ \--unpacked ] ]
+            [ \--pretty | \--header ]
+            [ \--bisect ]
+            <commit>... [ \-- <paths>... ]
+
+DESCRIPTION
+-----------
+Lists commit objects in reverse chronological order starting at the
+given commit(s), taking ancestry relationship into account.  This is
+useful to produce human-readable log output.
+
+Commits which are stated with a preceding '{caret}' cause listing to stop at
+that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus
+means "list all the commits which are included in 'foo' and 'bar', but
+not in 'baz'".
+
+A special notation <commit1>..<commit2> can be used as a
+short-hand for {caret}<commit1> <commit2>.
+
+
+OPTIONS
+-------
+--pretty::
+       Print the contents of the commit changesets in human-readable form.
+
+--header::
+       Print the contents of the commit in raw-format; each
+       record is separated with a NUL character.
+
+--objects::
+       Print the object IDs of any object referenced by the listed commits.
+       'git-rev-list --objects foo ^bar' thus means "send me all object IDs
+       which I need to download if I have the commit object 'bar', but
+       not 'foo'".
+
+--objects-edge::
+       Similar to `--objects`, but also print the IDs of
+       excluded commits refixed with a `-` character.  This is
+       used by `git-pack-objects` to build 'thin' pack, which
+       records objects in deltified form based on objects
+       contained in these excluded commits to reduce network
+       traffic.
+
+--unpacked::
+       Only useful with `--objects`; print the object IDs that
+       are not in packs.
+
+--bisect::
+       Limit output to the one commit object which is roughly halfway
+       between the included and excluded commits. Thus, if 'git-rev-list
+       --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output
+       of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint
+       {caret}bar {caret}baz' would be of roughly the same length.
+       Finding the change
+       which introduces a regression is thus reduced to a binary search:
+       repeatedly generate and test new 'midpoint's until the commit chain
+       is of length one.
+
+--max-count::
+       Limit the number of commits output.
+
+--max-age=timestamp, --min-age=timestamp::
+       Limit the commits output to specified time range.
+
+--sparse::
+       When optional paths are given, the command outputs only
+       the commits that changes at least one of them, and also
+       ignores merges that do not touch the given paths.  This
+       flag makes the command output all eligible commits
+       (still subject to count and age limitation), but apply
+       merge simplification nevertheless.
+
+--remove-empty::
+       Stop when a given path disappears from the tree.
+
+--all::
+       Pretend as if all the refs in `$GIT_DIR/refs/` are
+       listed on the command line as <commit>.
+
+--topo-order::
+       By default, the commits are shown in reverse
+       chronological order.  This option makes them appear in
+       topological order (i.e. descendant commits are shown
+       before their parents).
+
+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-rev-parse.txt b/Documentation/git-rev-parse.txt
new file mode 100644 (file)
index 0000000..627cde8
--- /dev/null
@@ -0,0 +1,203 @@
+git-rev-parse(1)
+================
+
+NAME
+----
+git-rev-parse - Pick out and massage parameters
+
+
+SYNOPSIS
+--------
+'git-rev-parse' [ --option ] <args>...
+
+DESCRIPTION
+-----------
+
+Many git porcelainish commands take mixture of flags
+(i.e. parameters that begin with a dash '-') and parameters
+meant for underlying `git-rev-list` command they use internally
+and flags and parameters for other commands they use as the
+downstream of `git-rev-list`.  This command is used to
+distinguish between them.
+
+
+OPTIONS
+-------
+--revs-only::
+       Do not output flags and parameters not meant for
+       `git-rev-list` command.
+
+--no-revs::
+       Do not output flags and parameters meant for
+       `git-rev-list` command.
+
+--flags::
+       Do not output non-flag parameters.
+
+--no-flags::
+       Do not output flag parameters.
+
+--default <arg>::
+       If there is no parameter given by the user, use `<arg>`
+       instead.
+
+--verify::
+       The parameter given must be usable as a single, valid
+       object name.  Otherwise barf and abort.
+
+--sq::
+       Usually the output is made one line per flag and
+       parameter.  This option makes output a single line,
+       properly quoted for consumption by shell.  Useful when
+       you expect your parameter to contain whitespaces and
+       newlines (e.g. when using pickaxe `-S` with
+       `git-diff-\*`).
+
+--not::
+       When showing object names, prefix them with '{caret}' and
+       strip '{caret}' prefix from the object names that already have
+       one.
+
+--symbolic::
+       Usually the object names are output in SHA1 form (with
+       possible '{caret}' prefix); this option makes them output in a
+       form as close to the original input as possible.
+
+
+--all::
+       Show all refs found in `$GIT_DIR/refs`.
+
+--branches::
+       Show branch refs found in `$GIT_DIR/refs/heads`.
+
+--tags::
+       Show tag refs found in `$GIT_DIR/refs/tags`.
+
+--remotes::
+       Show tag refs found in `$GIT_DIR/refs/remotes`.
+
+--show-prefix::
+       When the command is invoked from a subdirectory, show the
+       path of the current directory relative to the top-level
+       directory.
+
+--show-cdup::
+       When the command is invoked from a subdirectory, show the
+       path of the top-level directory relative to the current
+       directory (typically a sequence of "../", or an empty string).
+
+--git-dir::
+       Show `$GIT_DIR` if defined else show the path to the .git directory.
+
+--short, --short=number::
+       Instead of outputting the full SHA1 values of object names try to
+       abbreviate them to a shorter unique name. When no length is specified
+       7 is used. The minimum length is 4.
+
+--since=datestring, --after=datestring::
+       Parses the date string, and outputs corresponding
+       --max-age= parameter for git-rev-list command.
+
+--until=datestring, --before=datestring::
+       Parses the date string, and outputs corresponding
+       --min-age= parameter for git-rev-list command.
+
+<args>...::
+       Flags and parameters to be parsed.
+
+
+SPECIFYING REVISIONS
+--------------------
+
+A revision parameter typically, but not necessarily, names a
+commit object.  They use what is called an 'extended SHA1'
+syntax.
+
+* The full SHA1 object name (40-byte hexadecimal string), or
+  a substring of such that is unique within the repository.
+  E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both
+  name the same commit object if there are no other object in
+  your repository whose object name starts with dae86e.
+
+* A symbolic ref name.  E.g. 'master' typically means the commit
+  object referenced by $GIT_DIR/refs/heads/master.  If you
+  happen to have both heads/master and tags/master, you can
+  explicitly say 'heads/master' to tell git which one you mean.
+
+* A suffix '@' followed by a date specification enclosed in a brace
+  pair (e.g. '\{yesterday\}', '\{1 month 2 weeks 3 days 1 hour 1
+  second ago\}' or '\{1979-02-26 18:30:00\}') to specify the value
+  of the ref at a prior point in time.  This suffix may only be
+  used immediately following a ref name and the ref must have an
+  existing log ($GIT_DIR/logs/<ref>).
+
+* 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}'
+  is equivalent to 'rev{caret}1').  As a special rule,
+  'rev{caret}0' means the commit itself and is used when 'rev' is the
+  object name of a tag object that refers to a commit object.
+
+* A suffix '~<n>' to a revision parameter means the commit
+  object that is the <n>th generation grand-parent of the named
+  commit object, following only the first parent.  I.e. rev~3 is
+  equivalent to rev{caret}{caret}{caret} which is equivalent to\
+  rev{caret}1{caret}1{caret}1.
+
+* A suffix '{caret}' followed by an object type name enclosed in
+  brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
+  could be a tag, and dereference the tag recursively until an
+  object of that type is found or the object cannot be
+  dereferenced anymore (in which case, barf).  `rev{caret}0`
+  introduced earlier is a short-hand for `rev{caret}\{commit\}`.
+
+* A suffix '{caret}' followed by an empty brace pair
+  (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag,
+  and dereference the tag recursively until a non-tag object is
+  found.
+
+'git-rev-parse' also accepts a prefix '{caret}' to revision parameter,
+which is passed to 'git-rev-list'.  Two revision parameters
+concatenated with '..' is a short-hand for writing a range
+between them.  I.e. 'r1..r2' is equivalent to saying '{caret}r1 r2'
+
+Here is an illustration, by Jon Loeliger.  Both node B and C are
+a commit parents of commit node A.  Parent commits are ordered
+left-to-right.
+
+    G   H   I   J
+     \ /     \ /
+      D   E   F
+       \  |  /
+        \ | /
+         \|/
+          B     C
+           \   /
+            \ /
+             A
+
+    A =      = A^0
+    B = A^   = A^1     = A~1
+    C = A^2  = A^2
+    D = A^^  = A^1^1   = A~2
+    E = B^2  = A^^2
+    F = B^3  = A^^3
+    G = A^^^ = A^1^1^1 = A~3
+    H = D^2  = B^^2    = A^^^2  = A~2^2
+    I = F^   = B^3^    = A^^3^
+    J = F^2  = B^3^2   = A^^3^2
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+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-revert.txt b/Documentation/git-revert.txt
new file mode 100644 (file)
index 0000000..71f7815
--- /dev/null
@@ -0,0 +1,57 @@
+git-revert(1)
+=============
+
+NAME
+----
+git-revert - Revert an existing commit
+
+SYNOPSIS
+--------
+'git-revert' [--edit | --no-edit] [-n] <commit>
+
+DESCRIPTION
+-----------
+Given one existing commit, revert the change the patch introduces, and record a
+new commit that records it.  This requires your working tree to be clean (no
+modifications from the HEAD commit).
+
+OPTIONS
+-------
+<commit>::
+       Commit to revert.
+
+-e|--edit::
+       With this option, `git-revert` will let you edit the commit
+       message prior committing the revert. This is the default if
+       you run the command from a terminal.
+
+--no-edit::
+       With this option, `git-revert` will not start the commit
+       message editor.
+
+-n|--no-commit::
+       Usually the command automatically creates a commit with
+       a commit log message stating which commit was reverted.
+       This flag applies the change necessary to revert the
+       named commit to your working tree, but does not make the
+       commit.  In addition, when this option is used, your
+       working tree does not have to match the HEAD commit.
+       The revert is done against the beginning state of your
+       working tree.
++
+This is useful when reverting more than one commits'
+effect to your working tree in a row.
+
+
+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-rm.txt b/Documentation/git-rm.txt
new file mode 100644 (file)
index 0000000..66fc478
--- /dev/null
@@ -0,0 +1,92 @@
+git-rm(1)
+=========
+
+NAME
+----
+git-rm - Remove files from the working tree and from the index
+
+SYNOPSIS
+--------
+'git-rm' [-f] [-n] [-v] [--] <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".
+
+
+OPTIONS
+-------
+<file>...::
+       Files to remove from the index and optionally, from the
+       working tree as well.
+
+-f::
+       Remove files from the working tree as well as from the index.
+
+-n::
+        Don't actually remove the file(s), just show if they exist in
+        the index.
+
+-v::
+        Be verbose.
+
+\--::
+       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 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.
+
+
+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.
++
+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`.
+
+See Also
+--------
+gitlink:git-add[1]
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
new file mode 100644 (file)
index 0000000..ad1b9cf
--- /dev/null
@@ -0,0 +1,94 @@
+git-send-email(1)
+=================
+
+NAME
+----
+git-send-email - Send a collection of patches as emails
+
+
+SYNOPSIS
+--------
+'git-send-email' [options] <file|directory> [... file|directory]
+
+
+
+DESCRIPTION
+-----------
+Takes the patches given on the command line and emails them out.
+
+The header of the email is configurable by command line options.  If not
+specified on the command line, the user will be prompted with a ReadLine
+enabled interface to provide the necessary information.
+
+OPTIONS
+-------
+The options available are:
+
+--cc::
+       Specify a starting "Cc:" value for each email.
+
+--chain-reply-to, --no-chain-reply-to::
+       If this is set, each email will be sent as a reply to the previous
+       email sent.  If disabled with "--no-chain-reply-to", all emails after
+       the first will be sent as replies to the first email sent.  When using
+       this, it is recommended that the first file given be an overview of the
+       entire patch series.
+       Default is --chain-reply-to
+
+--compose::
+       Use $EDITOR to edit an introductory message for the
+       patch series.
+
+--from::
+       Specify the sender of the emails.  This will default to
+       the value GIT_COMMITTER_IDENT, as returned by "git-var -l".
+       The user will still be prompted to confirm this entry.
+
+--in-reply-to::
+       Specify the contents of the first In-Reply-To header.
+       Subsequent emails will refer to the previous email 
+       instead of this if --chain-reply-to is set (the default)
+       Only necessary if --compose is also set.  If --compose
+       is not set, this will be prompted for.
+
+--no-signed-off-by-cc::
+       Do not add emails found in Signed-off-by: lines to the cc list.
+
+--quiet::
+       Make git-send-email less verbose.  One line per email should be
+       all that is output.
+
+--smtp-server::
+       If set, specifies the outgoing SMTP server to use.  Defaults to
+       localhost.
+
+--subject::
+       Specify the initial subject of the email thread.
+       Only necessary if --compose is also set.  If --compose
+       is not set, this will be prompted for.
+
+--suppress-from::
+       Do not add the From: address to the cc: list, if it shows up in a From:
+       line.
+
+--to::
+       Specify the primary recipient of the emails generated.
+       Generally, this will be the upstream maintainer of the
+       project involved.
+
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com>
+
+git-send-email is originally based upon
+send_lots_of_email.pl by Greg Kroah-Hartman.
+
+Documentation
+--------------
+Documentation by Ryan Anderson
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
new file mode 100644 (file)
index 0000000..9e67f17
--- /dev/null
@@ -0,0 +1,110 @@
+git-send-pack(1)
+================
+
+NAME
+----
+git-send-pack - Push missing objects packed
+
+
+SYNOPSIS
+--------
+'git-send-pack' [--all] [--force] [--exec=<git-receive-pack>] [<host>:]<directory> [<ref>...]
+
+DESCRIPTION
+-----------
+Invokes 'git-receive-pack' on a possibly remote repository, and
+updates it from the current repository, sending named refs.
+
+
+OPTIONS
+-------
+--exec=<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::
+       Instead of explicitly specifying which refs to update,
+       update all refs that locally exist.
+
+--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.
+
+<host>::
+       A remote host to house the repository.  When this
+       part is specified, 'git-receive-pack' is invoked via
+       ssh.
+
+<directory>::
+       The repository to update.
+
+<ref>...:
+       The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+There are three ways to specify which refs to update on the
+remote end.
+
+With '--all' flag, all refs that exist locally are transferred to
+the remote side.  You cannot specify any '<ref>' if you use
+this flag.
+
+Without '--all' and without any '<ref>', the refs that exist
+both on the local side and on the remote side are updated.
+
+When one or more '<ref>' are specified explicitly, it can be either a
+single pattern, or a pair of such pattern separated by a colon
+":" (this means that a ref name cannot have a colon in it).  A
+single pattern '<name>' is just a shorthand for '<name>:<name>'.
+
+Each pattern pair consists of the source side (before the colon)
+and the destination side (after the colon).  The ref to be
+pushed is determined by finding a match that matches the source
+side, and where it is pushed is determined by using the
+destination side.
+
+ - It is an error if <src> does not match exactly one of the
+   local refs.
+
+ - It is an error if <dst> matches more than one remote refs.
+
+ - If <dst> does not match any remote ref, either
+
+   * it has to start with "refs/"; <dst> is used as the
+     destination literally in this case.
+
+   * <src> == <dst> and the ref that matched the <src> must not
+     exist in the set of remote refs; the ref matched <src>
+     locally is used as the name of the destination.
+
+Without '--force', the <src> ref is stored at the remote only if
+<dst> does not exist, or <dst> is a proper subset (i.e. an
+ancestor) of <src>.  This check, known as "fast forward check",
+is performed in order to avoid accidentally overwriting the
+remote ref and lose other peoples' commits from there.
+
+With '--force', the fast forward check is disabled for all refs.
+
+Optionally, a <ref> parameter can be prefixed with a plus '+' sign
+to disable the fast-forward check only on that ref.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
new file mode 100644 (file)
index 0000000..79217d8
--- /dev/null
@@ -0,0 +1,35 @@
+git-sh-setup(1)
+===============
+
+NAME
+----
+git-sh-setup - Common git shell script setup code
+
+SYNOPSIS
+--------
+'git-sh-setup'
+
+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"
+-------------------------------------------------
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
new file mode 100644 (file)
index 0000000..cc4266d
--- /dev/null
@@ -0,0 +1,35 @@
+git-shell(1)
+============
+
+NAME
+----
+git-shell - Restricted login shell for GIT over SSH only
+
+
+SYNOPSIS
+--------
+'git-shell' -c <command> <argument>
+
+DESCRIPTION
+-----------
+This is meant to be used as a login shell for SSH accounts you want
+to restrict to GIT pull/push access only. It permits execution only
+of server-side GIT commands implementing the pull/push functionality.
+The commands can be executed only by the '-c' option; the shell is not
+interactive.
+
+Currently, only the `git-receive-pack` and `git-upload-pack` commands
+are permitted to be called, with a single required argument.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
new file mode 100644 (file)
index 0000000..54fb922
--- /dev/null
@@ -0,0 +1,30 @@
+git-shortlog(1)
+===============
+
+NAME
+----
+git-shortlog - Summarize 'git log' output
+
+
+SYNOPSIS
+--------
+git-log --pretty=short | 'git-shortlog'
+
+DESCRIPTION
+-----------
+Summarizes 'git log' output in a format suitable for inclusion
+in release announcements.
+
+
+Author
+------
+Written by Jeff Garzik <jgarzik@pobox.com>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
new file mode 100644 (file)
index 0000000..f115b45
--- /dev/null
@@ -0,0 +1,162 @@
+git-show-branch(1)
+==================
+
+NAME
+----
+git-show-branch - Show branches and their commits
+
+SYNOPSIS
+--------
+[verse]
+'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+               [--more=<n> | --list | --independent | --merge-base]
+               [--no-name | --sha1-name] [<rev> | <glob>]...
+
+DESCRIPTION
+-----------
+
+Shows the commit ancestry graph starting from the commits named
+with <rev>s or <globs>s (or all refs under $GIT_DIR/refs/heads
+and/or $GIT_DIR/refs/tags) semi-visually.
+
+It cannot show more than 29 branches and commits at a time.
+
+It uses `showbranch.default` multi-valued configuration items if
+no <rev> nor <glob> is given on the command line.
+
+
+OPTIONS
+-------
+<rev>::
+       Arbitrary extended SHA1 expression (see `git-rev-parse`)
+       that typically names a branch HEAD or a tag.
+
+<glob>::
+       A glob pattern that matches branch or tag names under
+       $GIT_DIR/refs.  For example, if you have many topic
+       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.
+
+--current::
+       With this option, the command includes the current
+       branch to the list of revs to be shown when it is not
+       given on the command line.
+
+--topo-order::
+        By default, the branches and their commits are shown in
+        reverse chronological order.  This option makes them
+        appear in topological order (i.e., descendant commits
+        are shown before their parents).
+
+--more=<n>::
+       Usually the command stops output upon showing the commit
+       that is the common ancestor of all the branches.  This
+       flag tells the command to go <n> more common commits
+       beyond that.  When <n> is negative, display only the
+       <reference>s given, without showing the commit ancestry
+       tree.
+
+--list::
+       Synonym to `--more=-1`
+
+--merge-base::
+       Instead of showing the commit list, just act like the
+       'git-merge-base -a' command, except that it can accept
+       more than two heads.
+
+--independent::
+       Among the <reference>s given, display only the ones that
+       cannot be reached from any other <reference>.
+
+--no-name::
+       Do not show naming strings for each commit.
+
+--sha1-name::
+       Instead of naming the commits using the path to reach
+       them from heads (e.g. "master~2" to mean the grandparent
+       of "master"), name them with the unique prefix of their
+       object names.
+
+Note that --more, --list, --independent and --merge-base options
+are mutually exclusive.
+
+
+OUTPUT
+------
+Given N <references>, the first N lines are the one-line
+description from their commit message.  The branch head that is
+pointed at by $GIT_DIR/HEAD is prefixed with an asterisk `*`
+character while other heads are prefixed with a `!` character.
+
+Following these N lines, one-line log for each commit is
+displayed, indented N places.  If a commit is on the I-th
+branch, the I-th indentation character shows a `+` sign;
+otherwise it shows a space.  Merge commits are denoted by
+a `-` sign.  Each commit shows a short name that
+can be used as an extended SHA1 to name that commit.
+
+The following example shows three branches, "master", "fixes"
+and "mhf":
+
+------------------------------------------------
+$ git show-branch master fixes mhf
+* [master] Add 'git show-branch'.
+ ! [fixes] Introduce "reset type" flag to "git reset"
+  ! [mhf] Allow "+remote:local" refspec to cause --force when fetching.
+---
+  + [mhf] Allow "+remote:local" refspec to cause --force when fetching.
+  + [mhf~1] Use git-octopus when pulling more than one heads.
+ +  [fixes] Introduce "reset type" flag to "git reset"
+  + [mhf~2] "git fetch --force".
+  + [mhf~3] Use .git/remote/origin, not .git/branches/origin.
+  + [mhf~4] Make "git pull" and "git fetch" default to origin
+  + [mhf~5] Infamous 'octopus merge'
+  + [mhf~6] Retire git-parse-remote.
+  + [mhf~7] Multi-head fetch.
+  + [mhf~8] Start adding the $GIT_DIR/remotes/ support.
+*++ [master] Add 'git show-branch'.
+------------------------------------------------
+
+These three branches all forked from a common commit, [master],
+whose commit message is "Add 'git show-branch'.  "fixes" branch
+adds one commit 'Introduce "reset type"'.  "mhf" branch has many
+other commits.  The current branch is "master".
+
+
+EXAMPLE
+-------
+
+If you keep your primary branches immediately under
+`$GIT_DIR/refs/heads`, and topic branches in subdirectories of
+it, having the following in the configuration file may help:
+
+------------
+[showbranch]
+       default = --topo-order
+       default = heads/*
+
+------------
+
+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.
+
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt
new file mode 100644 (file)
index 0000000..be09b62
--- /dev/null
@@ -0,0 +1,35 @@
+git-show-index(1)
+=================
+
+NAME
+----
+git-show-index - Show packed archive index
+
+
+SYNOPSIS
+--------
+'git-show-index' < idx-file
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed git archive created with
+git-pack-objects command, and dumps its contents.
+
+The information it outputs is subset of what you can get from
+'git-verify-pack -v'; this command only shows the packfile
+offset and SHA1 of each object.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
new file mode 100644 (file)
index 0000000..2b4df3f
--- /dev/null
@@ -0,0 +1,49 @@
+git-show(1)
+===========
+
+NAME
+----
+git-show - Show one commit with difference it introduces
+
+
+SYNOPSIS
+--------
+'git-show' <option>...
+
+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'.
+
+This manual page describes only the most frequently used options.
+
+
+OPTIONS
+-------
+<commitid>::
+       ID of the commit to show.
+
+--pretty=<format>::
+       Controls the output format for the commit logs.
+       <format> can be one of 'raw', 'medium', 'short', 'full',
+       and 'oneline'.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+-------------
+Documentation by David Greaves, Petr Baudis and the git-list <git@vger.kernel.org>.
+
+This manual page is a stub. You can help the git documentation by expanding it.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ssh-fetch.txt b/Documentation/git-ssh-fetch.txt
new file mode 100644 (file)
index 0000000..b7116b3
--- /dev/null
@@ -0,0 +1,51 @@
+git-ssh-fetch(1)
+================
+
+NAME
+----
+git-ssh-fetch - Pulls from a remote repository over ssh connection
+
+
+
+SYNOPSIS
+--------
+'git-ssh-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pulls from a remote repository over ssh connection, invoking
+git-ssh-upload on the other end. It functions identically to
+git-ssh-upload, aside from which end you run it on.
+
+
+OPTIONS
+-------
+commit-id::
+        Either the hash or the filename under [URL]/refs/ to
+        pull.
+
+-c::
+       Get the commit objects.
+-t::
+       Get trees associated with the commit objects.
+-a::
+       Get all the objects.
+-v::
+       Report what is downloaded.
+-w::
+        Writes the commit-id into the filename under $GIT_DIR/refs/ on
+        the local end after the transfer is complete.
+
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.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-ssh-upload.txt b/Documentation/git-ssh-upload.txt
new file mode 100644 (file)
index 0000000..702674e
--- /dev/null
@@ -0,0 +1,47 @@
+git-ssh-upload(1)
+=================
+
+NAME
+----
+git-ssh-upload - Pushes to a remote repository over ssh connection
+
+
+SYNOPSIS
+--------
+'git-ssh-upload' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pushes from a remote repository over ssh connection, invoking
+git-ssh-fetch on the other end. It functions identically to
+git-ssh-fetch, aside from which end you run it on.
+
+OPTIONS
+-------
+commit-id::
+        Id of commit to push.
+
+-c::
+        Get the commit objects.
+-t::
+        Get tree associated with the requested commit object.
+-a::
+        Get all the objects.
+-v::
+        Report what is uploaded.
+-w::
+        Writes the commit-id into the filename under [URL]/refs/ on
+        the remote end after the transfer is complete.
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.org>
+
+Documentation
+--------------
+Documentation by Daniel Barkalow
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
new file mode 100644 (file)
index 0000000..e446f48
--- /dev/null
@@ -0,0 +1,45 @@
+git-status(1)
+=============
+
+NAME
+----
+git-status - Show working tree status
+
+
+SYNOPSIS
+--------
+'git-status'
+
+DESCRIPTION
+-----------
+Examines paths in the working tree that has changes unrecorded
+to the index file, and changes between the index file and the
+current HEAD commit.  The former paths are what you _could_
+commit by running 'git-update-index' before running 'git
+commit', and the latter paths are what you _would_ commit by
+running 'git commit'.
+
+If there is no path that is different between the index file and
+the current HEAD commit, the command exits with non-zero
+status.
+
+
+OUTPUT
+------
+The output from this command is designed to be used as a commit
+template comments, and all the output lines are prefixed with '#'.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>.
+
+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-stripspace.txt b/Documentation/git-stripspace.txt
new file mode 100644 (file)
index 0000000..3a03dd0
--- /dev/null
@@ -0,0 +1,33 @@
+git-stripspace(1)
+=================
+
+NAME
+----
+git-stripspace - Filter out empty lines
+
+
+SYNOPSIS
+--------
+'git-stripspace' < <stream>
+
+DESCRIPTION
+-----------
+Remove multiple empty lines, and empty lines at beginning and end.
+
+OPTIONS
+-------
+<stream>::
+       Byte stream to act on.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
new file mode 100644 (file)
index 0000000..b1b87c2
--- /dev/null
@@ -0,0 +1,160 @@
+git-svnimport(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-svnimport - Import a SVN repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
+               [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
+               [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
+               [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
+               [ -I <ignorefile_name> ] [ -A <author_file> ]
+               <SVN_repository_URL> [ <path> ]
+
+
+DESCRIPTION
+-----------
+Imports a SVN repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+SVN access is done by the SVN::Perl module.
+
+git-svnimport assumes that SVN repositories are organized into one
+"trunk" directory where the main development happens, "branch/FOO"
+directories for branches, and "/tags/FOO" directories for tags.
+Other subdirectories are ignored.
+
+git-svnimport creates a file ".git/svn2git", which is required for
+incremental SVN imports.
+
+OPTIONS
+-------
+-C <target-dir>::
+        The GIT repository to import to.  If the directory doesn't
+        exist, it will be created.  Default is the current directory.
+
+-s <start_rev>::
+        Start importing at this SVN change number. The  default is 1.
++
+When importing incrementally, you might need to edit the .git/svn2git file.
+
+-i::
+       Import-only: don't perform a checkout after importing.  This option
+       ensures the working directory and index remain untouched and will
+       not create them if they do not exist.
+
+-T <trunk_subdir>::
+       Name the SVN trunk. Default "trunk".
+
+-t <tag_subdir>::
+       Name the SVN subdirectory for tags. Default "tags".
+
+-b <branch_subdir>::
+       Name the SVN subdirectory for branches. Default "branches".
+
+-o <branch-for-HEAD>::
+       The 'trunk' branch from SVN is imported to the 'origin' branch within
+       the git repository. Use this option if you want to import into a
+       different branch.
+
+-r::
+       Prepend 'rX: ' to commit messages, where X is the imported
+       subversion revision.
+
+-I <ignorefile_name>::
+       Import the svn:ignore directory property to files with this
+       name in each directory. (The Subversion and GIT ignore
+       syntaxes are similar enough that using the Subversion patterns
+       directly with "-I .gitignore" will almost always just work.)
+
+-A <author_file>::
+       Read a file with lines on the form
++
+------
+       username = User's Full Name <email@addr.es>
+
+------
++
+and use "User's Full Name <email@addr.es>" as the GIT
+author and committer for Subversion commits made by
+"username". If encountering a commit made by a user not in the
+list, abort.
++
+For convenience, this data is saved to $GIT_DIR/svn-authors
+each time the -A option is provided, and read from that same
+file each time git-svnimport is run with an existing GIT
+repository without -A.
+
+-m::
+       Attempt to detect merges based on the commit message. This option
+       will enable default regexes that try to capture the name source
+       branch name from the commit message.
+
+-M <regex>::
+       Attempt to detect merges based on the commit message with a custom
+       regex. It can be used with -m to also see the default regexes.
+       You must escape forward slashes.
+
+-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.)
+
+-v::
+       Verbosity: let 'svnimport' report what it is doing.
+
+-d::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       only for retrieving the SVN logs; the path to the contents is
+       included in the SVN log.
+
+-D::
+       Use direct HTTP requests if possible. The "<path>" argument is used
+       for retrieving the logs, as well as for the contents.
++
+There's no safe way to automatically find out which of these options to
+use, so you need to try both. Usually, the one that's wrong will die
+with a 40x error pretty quickly.
+
+<SVN_repository_URL>::
+       The URL of the SVN module you want to import. For local
+       repositories, use "file:///absolute/path".
++
+If you're using the "-d" or "-D" option, this is the URL of the SVN
+repository itself; it usually ends in "/svn".
+
+<path>::
+       The path to the module you want to check out.
+
+-h::
+       Print a short usage message and exit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Based on a cvs2git script by the same author.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
new file mode 100644 (file)
index 0000000..68ac6a6
--- /dev/null
@@ -0,0 +1,52 @@
+git-symbolic-ref(1)
+===================
+
+NAME
+----
+git-symbolic-ref - read and modify symbolic refs
+
+SYNOPSIS
+--------
+'git-symbolic-ref' <name> [<ref>]
+
+DESCRIPTION
+-----------
+Given one argument, reads which branch head the given symbolic
+ref refers to and outputs its path, relative to the `.git/`
+directory.  Typically you would give `HEAD` as the <name>
+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
+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.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
new file mode 100644 (file)
index 0000000..45476c2
--- /dev/null
@@ -0,0 +1,75 @@
+git-tag(1)
+==========
+
+NAME
+----
+git-tag - Create a tag object signed with GPG
+
+
+SYNOPSIS
+--------
+[verse]
+'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' -l [<pattern>]
+
+DESCRIPTION
+-----------
+Adds a 'tag' reference in `.git/refs/tags/`
+
+Unless `-f` is given, the tag must not yet exist in
+`.git/refs/tags/` directory.
+
+If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+creates a 'tag' object, and requires the tag message.  Unless
+`-m <msg>` is given, an editor is started for the user to type
+in the tag message.
+
+Otherwise just the SHA1 object name of the commit object is
+written (i.e. a lightweight tag).
+
+A GnuPG signed tag object will be created when `-s` or `-u
+<key-id>` is used.  When `-u <key-id>` is not used, the
+committer identity for the current user is used to find the
+GnuPG key for signing.
+
+`-d <tag>` deletes the tag.
+
+`-l <pattern>` lists tags that match the given pattern (or all
+if no pattern is given).
+
+OPTIONS
+-------
+-a::
+       Make an unsigned, annotated tag object
+
+-s::
+       Make a GPG-signed tag, using the default e-mail address's key
+
+-u <key-id>::
+       Make a GPG-signed tag, using the given key
+
+-f::
+       Replace an existing tag with the given name (instead of failing)
+
+-d::
+       Delete an existing tag with the given name
+
+-l <pattern>::
+       List tags that match the given pattern (or all if no pattern is given).
+
+-m <msg>::
+       Use the given tag message (instead of prompting)
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>,
+Junio C Hamano <junkio@cox.net> and Chris Wright <chrisw@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-tar-tree.txt b/Documentation/git-tar-tree.txt
new file mode 100644 (file)
index 0000000..2139b6f
--- /dev/null
@@ -0,0 +1,38 @@
+git-tar-tree(1)
+===============
+
+NAME
+----
+git-tar-tree - Creates a tar archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-tar-tree' <tree-ish> [ <base> ]
+
+DESCRIPTION
+-----------
+Creates a tar archive containing the tree structure for the named tree.
+When <base> is specified it is added as a leading path to the files in the
+generated tar archive.
+
+git-tar-tree behaves differently when given a tree ID versus when given
+a commit ID or tag ID.  In the first case the current time is used as
+modification time of each file in the archive.  In the latter case the
+commit time as recorded in the referenced commit object is used instead.
+Additionally the commit ID is stored in a global extended pax header.
+It can be extracted using git-get-tar-commit-id.
+
+
+Author
+------
+Written by Rene Scharfe.
+
+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-tools.txt b/Documentation/git-tools.txt
new file mode 100644 (file)
index 0000000..d79523f
--- /dev/null
@@ -0,0 +1,97 @@
+A short git tools survey
+========================
+
+
+Introduction
+------------
+
+Apart from git contrib/ area there are some others third-party tools
+you may want to look.
+
+This document presents a brief summary of each tool and the corresponding
+link.
+
+
+Alternative/Augmentative Porcelains
+-----------------------------------
+
+   - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
+
+   Cogito is a version control system layered on top of the git tree history
+   storage system. It aims at seamless user interface and ease of use,
+   providing generally smoother user experience than the "raw" Core GIT
+   itself and indeed many other version control systems.
+
+
+   - *pg* (http://www.spearce.org/category/projects/scm/pg/)
+
+   pg is a shell script wrapper around GIT to help the user manage a set of
+   patches to files. pg is somewhat like quilt or StGIT, but it does have a
+   slightly different feature set.
+
+
+   - *StGit* (http://www.procode.org/stgit/)
+
+   Stacked GIT provides a quilt-like patch management functionality in the
+    GIT environment. You can easily manage your patches in the scope of GIT
+   until they get merged upstream.
+
+
+History Viewers
+---------------
+
+   - *gitk* (shipped with git-core)
+
+   gitk is a simple TK GUI for browsing history of GIT repositories easily.
+
+
+   - *gitview*  (contrib/)
+
+   gitview is a GTK based repository browser for git
+
+
+   - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+
+   GITweb provides full-fledged web interface for GIT repositories.
+
+
+   - *qgit* (http://digilander.libero.it/mcostalba/)
+
+   QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used
+   to browse history and directory tree, view annotated files, commit
+   changes cherry picking single files or applying patches.
+   Currently it is the fastest and most feature rich among the git
+   viewers and commit tools.
+
+
+
+Foreign SCM interface
+---------------------
+
+   - *git-svn* (contrib/)
+
+   git-svn is a simple conduit for changesets between a single Subversion
+   branch and git.
+
+
+   - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
+
+   These utilities convert patch series in a quilt repository and commit
+   series in git back and forth.
+
+
+Others
+------
+
+   - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
+
+   Commit Tool or (h)gct is a GUI enabled commit tool for git and
+   Mercurial (hg). It allows the user to view diffs, select which files
+   to committed (or ignored / reverted) write commit messages and
+   perform the commit itself.
+
+   - *git.el* (contrib/)
+
+   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.
diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt
new file mode 100644 (file)
index 0000000..213dc81
--- /dev/null
@@ -0,0 +1,36 @@
+git-unpack-file(1)
+==================
+
+NAME
+----
+git-unpack-file - Creates a temporary file with a blob's contents
+
+
+
+SYNOPSIS
+--------
+'git-unpack-file' <blob>
+
+DESCRIPTION
+-----------
+Creates a file holding the contents of the blob specified by sha1. It
+returns the name of the temporary file in the following format:
+       .merge_file_XXXXX
+
+OPTIONS
+-------
+<blob>::
+       Must be a blob id
+
+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-unpack-objects.txt b/Documentation/git-unpack-objects.txt
new file mode 100644 (file)
index 0000000..c20b38b
--- /dev/null
@@ -0,0 +1,49 @@
+git-unpack-objects(1)
+=====================
+
+NAME
+----
+git-unpack-objects - Unpack objects from a packed archive
+
+
+SYNOPSIS
+--------
+'git-unpack-objects' [-n] [-q] <pack-file
+
+
+DESCRIPTION
+-----------
+Read a packed archive (.pack) from the standard input, expanding
+the objects contained within and writing them into the repository in
+"loose" (one object per file) format.
+
+Objects that already exist in the repository will *not* be unpacked
+from the pack-file.  Therefore, nothing will be unpacked if you use
+this command on a pack-file that exists within the target repository.
+
+Please see the `git-repack` documentation for options to generate
+new packs and replace existing ones.
+
+OPTIONS
+-------
+-n::
+        Only list the objects that would be unpacked, don't actually unpack
+        them.
+
+-q::
+       The command usually shows percentage progress.  This
+       flag suppresses it.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
new file mode 100644 (file)
index 0000000..3ae6e74
--- /dev/null
@@ -0,0 +1,318 @@
+git-update-index(1)
+===================
+
+NAME
+----
+git-update-index - Modifies the index or directory cache
+
+
+SYNOPSIS
+--------
+[verse]
+'git-update-index'
+            [--add] [--remove | --force-remove] [--replace]
+            [--refresh] [-q] [--unmerged] [--ignore-missing]
+            [--cacheinfo <mode> <object> <file>]\*
+            [--chmod=(+|-)x]
+            [--assume-unchanged | --no-assume-unchanged]
+            [--really-refresh] [--unresolve] [--again]
+            [--info-only] [--index-info]
+            [-z] [--stdin]
+            [--verbose]
+            [--] [<file>]\*
+
+DESCRIPTION
+-----------
+Modifies the index or directory cache. Each file mentioned is updated
+into the index and any 'unmerged' or 'needs updating' state is
+cleared.
+
+The way "git-update-index" handles files it is told about can be modified
+using the various options:
+
+OPTIONS
+-------
+--add::
+       If a specified file isn't in the index already then it's
+       added.
+       Default behaviour is to ignore new files.
+
+--remove::
+       If a specified file is in the index but is missing then it's
+       removed.
+       Default behavior is to ignore removed file.
+
+--refresh::
+       Looks at the current index and checks to see if merges or
+       updates are needed by checking stat() information.
+
+-q::
+        Quiet.  If --refresh finds that the index needs an update, the
+        default behavior is to error out.  This option makes
+        git-update-index continue anyway.
+
+--unmerged::
+        If --refresh finds unmerged changes in the index, the default
+        behavior is to error out.  This option makes git-update-index 
+        continue anyway.
+
+--ignore-missing::
+       Ignores missing files during a --refresh
+
+--cacheinfo <mode> <object> <path>::
+       Directly insert the specified info into the index.
+       
+--index-info::
+        Read index information from stdin.
+
+--chmod=(+|-)x::
+        Set the execute permissions on the updated files.        
+
+--assume-unchanged, --no-assume-unchanged::
+       When these flags are specified, the object name recorded
+       for the paths are not updated.  Instead, these options
+       sets and unsets the "assume unchanged" bit for the
+       paths.  When the "assume unchanged" bit is on, git stops
+       checking the working tree files for possible
+       modifications, so you need to manually unset the bit to
+       tell git when you change the working tree file. This is
+       sometimes helpful when working with a big project on a
+       filesystem that has very slow lstat(2) system call
+       (e.g. cifs).
+
+--again::
+       Runs `git-update-index` itself on the paths whose index
+       entries are different from those from the `HEAD` commit.
+
+--unresolve::
+       Restores the 'unmerged' or 'needs updating' state of a
+       file during a merge if it was cleared by accident.
+
+--info-only::
+       Do not create objects in the object database for all
+       <file> arguments that follow this flag; just insert
+       their object IDs into the index.
+
+--force-remove::
+       Remove the file from the index even when the working directory
+       still has such a file. (Implies --remove.)
+
+--replace::
+       By default, when a file `path` exists in the index,
+       git-update-index refuses an attempt to add `path/file`.
+       Similarly if a file `path/file` exists, a file `path`
+       cannot be added.  With --replace flag, existing entries
+       that conflicts with the entry being added are
+       automatically removed with warning messages.
+
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+--verbose::
+        Report what is being added and removed from index.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
+
+\--::
+       Do not interpret any more arguments as options.
+
+<file>::
+       Files to act on.
+       Note that files beginning with '.' are discarded. This includes
+       `./file` and `dir/./file`. If you don't want this, then use     
+       cleaner names.
+       The same applies to directories ending '/' and paths with '//'
+
+Using --refresh
+---------------
+'--refresh' does not calculate a new sha1 file or bring the index
+up-to-date for mode/content changes. But what it *does* do is to
+"re-match" the stat information of a file with the index, so that you
+can refresh the index for a file that hasn't been changed but where
+the stat entry is out of date.
+
+For example, you'd want to do this after doing a "git-read-tree", to link
+up the stat index details with the proper files.
+
+Using --cacheinfo or --info-only
+--------------------------------
+'--cacheinfo' is used to register a file that is not in the
+current working directory.  This is useful for minimum-checkout
+merging.
+
+To pretend you have a file with mode and sha1 at path, say:
+
+----------------
+$ git-update-index --cacheinfo mode sha1 path
+----------------
+
+'--info-only' is used to register files without placing them in the object
+database.  This is useful for status-only repositories.
+
+Both '--cacheinfo' and '--info-only' behave similarly: the index is updated
+but the object database isn't.  '--cacheinfo' is useful when the object is
+in the database but the file isn't available locally.  '--info-only' is
+useful when the file is available, but you do not wish to update the
+object database.
+
+
+Using --index-info
+------------------
+
+`--index-info` is a more powerful mechanism that lets you feed
+multiple entry definitions from the standard input, and designed
+specifically for scripts.  It can take inputs of three formats:
+
+    . mode         SP sha1          TAB path
++
+The first format is what "git-apply --index-info"
+reports, and used to reconstruct a partial tree
+that is used for phony merge base tree when falling
+back on 3-way merge.
+
+    . mode SP type SP sha1          TAB path
++
+The second format is to stuff git-ls-tree output
+into the index file.
+
+    . mode         SP sha1 SP stage TAB path
++
+This format is to put higher order stages into the
+index file and matches git-ls-files --stage output.
+
+To place a higher stage entry to the index, the path should
+first be removed by feeding a mode=0 entry for the path, and
+then feeding necessary input lines in the third format.
+
+For example, starting with this index:
+
+------------
+$ git ls-files -s
+100644 8a1218a1024a212bb3db30becd860315f9f3ac52 0       frotz
+------------
+
+you can feed the following input to `--index-info`:
+
+------------
+$ git update-index --index-info
+0 0000000000000000000000000000000000000000     frotz
+100644 8a1218a1024a212bb3db30becd860315f9f3ac52 1      frotz
+100755 8a1218a1024a212bb3db30becd860315f9f3ac52 2      frotz
+------------
+
+The first line of the input feeds 0 as the mode to remove the
+path; the SHA1 does not matter as long as it is well formatted.
+Then the second and third line feeds stage 1 and stage 2 entries
+for that path.  After the above, we would end up with this:
+
+------------
+$ git ls-files -s
+100644 8a1218a1024a212bb3db30becd860315f9f3ac52 1      frotz
+100755 8a1218a1024a212bb3db30becd860315f9f3ac52 2      frotz
+------------
+
+
+Using "assume unchanged" bit
+----------------------------
+
+Many operations in git depend on your filesystem to have an
+efficient `lstat(2)` implementation, so that `st_mtime`
+information for working tree files can be cheaply checked to see
+if the file contents have changed from the version recorded in
+the index file.  Unfortunately, some filesystems have
+inefficient `lstat(2)`.  If your filesystem is one of them, you
+can set "assume unchanged" bit to paths you have not changed to
+cause git not to do this check.  Note that setting this bit on a
+path does not mean git will check the contents of the file to
+see if it has changed -- it makes git to omit any checking and
+assume it has *not* changed.  When you make changes to working
+tree files, you have to explicitly tell git about it by dropping
+"assume unchanged" bit, either before or after you modify them.
+
+In order to set "assume unchanged" bit, use `--assume-unchanged`
+option.  To unset, use `--no-assume-unchanged`.
+
+The command looks at `core.ignorestat` configuration variable.  When
+this is true, paths updated with `git-update-index paths...` and
+paths updated with other git commands that update both index and
+working tree (e.g. `git-apply --index`, `git-checkout-index -u`,
+and `git-read-tree -u`) are automatically marked as "assume
+unchanged".  Note that "assume unchanged" bit is *not* set if
+`git-update-index --refresh` finds the working tree file matches
+the index (use `git-update-index --really-refresh` if you want
+to mark them as "assume unchanged").
+
+
+Examples
+--------
+To update and refresh only the files already checked out:
+
+----------------
+$ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
+----------------
+
+On an inefficient filesystem with `core.ignorestat` set::
++
+------------
+$ git update-index --really-refresh              <1>
+$ git update-index --no-assume-unchanged foo.c   <2>
+$ git diff --name-only                           <3>
+$ edit foo.c
+$ git diff --name-only                           <4>
+M foo.c
+$ git update-index foo.c                         <5>
+$ git diff --name-only                           <6>
+$ edit foo.c
+$ git diff --name-only                           <7>
+$ git update-index --no-assume-unchanged foo.c   <8>
+$ git diff --name-only                           <9>
+M foo.c
+------------
++
+<1> forces lstat(2) to set "assume unchanged" bits for paths that match index.
+<2> mark the path to be edited.
+<3> this does lstat(2) and finds index matches the path.
+<4> this does lstat(2) and finds index does *not* match the path.
+<5> registering the new version to index sets "assume unchanged" bit.
+<6> and it is assumed unchanged.
+<7> even after you edit it.
+<8> you can tell about the change after the fact.
+<9> now it checks with lstat(2) and finds it has been changed.
+
+
+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]).
+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
+need to use `git-update-index --chmod=`.
+
+The command looks at `core.ignorestat` configuration variable.  See
+'Using "assume unchanged" bit' section above.
+
+
+See Also
+--------
+gitlink:git-repo-config[1]
+
+
+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-update-ref.txt b/Documentation/git-update-ref.txt
new file mode 100644 (file)
index 0000000..e062030
--- /dev/null
@@ -0,0 +1,84 @@
+git-update-ref(1)
+=================
+
+NAME
+----
+git-update-ref - update the object name stored in a ref safely
+
+SYNOPSIS
+--------
+'git-update-ref' [-m <reason>] <ref> <newvalue> [<oldvalue>]
+
+DESCRIPTION
+-----------
+Given two arguments, stores the <newvalue> in the <ref>, possibly
+dereferencing the symbolic refs.  E.g. `git-update-ref HEAD
+<newvalue>` updates the current branch head to the new object.
+
+Given three arguments, stores the <newvalue> in the <ref>,
+possibly dereferencing the symbolic refs, after verifying that
+the current value of the <ref> matches <oldvalue>.
+E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
+updates the master branch head to <newvalue> only if its current
+value is <oldvalue>.
+
+It also allows a "ref" file to be a symbolic pointer to another
+ref file by starting with the four-byte header sequence of
+"ref:".
+
+More importantly, it allows the update of a ref file to follow
+these symbolic pointers, whether they are symlinks or these
+"regular file symbolic refs".  It follows *real* symlinks only
+if they start with "refs/": otherwise it will just try to read
+them and update them as a regular file (i.e. it will allow the
+filesystem to follow them, but will overwrite such a symlink to
+somewhere else with a regular filename).
+
+In general, using
+
+       git-update-ref HEAD "$head"
+
+should be a _lot_ safer than doing
+
+       echo "$head" > "$GIT_DIR/HEAD"
+
+both from a symlink following standpoint *and* an error checking
+standpoint.  The "refs/" rule for symlinks means that symlinks
+that point to "outside" the tree are safe: they'll be followed
+for reading but not for writing (so we'll never write through a
+ref symlink to some other tree, if you have copied a whole
+archive by creating a symlink tree).
+
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value.  Log lines are formatted as:
+
+    . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+    . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt
new file mode 100644 (file)
index 0000000..88a03c7
--- /dev/null
@@ -0,0 +1,58 @@
+git-update-server-info(1)
+=========================
+
+NAME
+----
+git-update-server-info - Update auxiliary info file to help dumb servers
+
+
+SYNOPSIS
+--------
+'git-update-server-info' [--force]
+
+DESCRIPTION
+-----------
+A dumb server that does not do on-the-fly pack generations must
+have some auxiliary information files in $GIT_DIR/info and
+$GIT_OBJECT_DIRECTORY/info directories to help clients discover
+what references and packs the server has.  This command
+generates such auxiliary files.
+
+
+OPTIONS
+-------
+
+-f|--force::
+       Update the info files from scratch.
+
+
+OUTPUT
+------
+
+Currently the command updates the following files.  Please see
+link:repository-layout.html[repository-layout] for description
+of what they are for:
+
+* objects/info/packs
+
+* info/refs
+
+
+BUGS
+----
+When you remove an existing ref, the command fails to update
+info/refs file unless `--force` flag is given.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
new file mode 100644 (file)
index 0000000..4795e98
--- /dev/null
@@ -0,0 +1,39 @@
+git-upload-pack(1)
+==================
+
+NAME
+----
+git-upload-pack - Send missing objects packed
+
+
+SYNOPSIS
+--------
+'git-upload-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-clone-pack' and/or 'git-fetch-pack', learns what
+objects the other side is missing, and sends them after packing.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-fetch-pack' side, and the
+program pair is meant to be used to pull updates from a remote
+repository.  For push operations, see 'git-send-pack'.
+
+
+OPTIONS
+-------
+<directory>::
+       The repository to sync from.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
new file mode 100644 (file)
index 0000000..a5b1a0d
--- /dev/null
@@ -0,0 +1,65 @@
+git-var(1)
+==========
+
+NAME
+----
+git-var - Print the git users identity
+
+
+SYNOPSIS
+--------
+'git-var' [ -l | <variable> ]
+
+DESCRIPTION
+-----------
+Prints a git logical variable.
+
+OPTIONS
+-------
+-l::
+       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`.)
+
+EXAMPLE
+--------
+       $ git-var GIT_AUTHOR_IDENT
+       Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+
+
+VARIABLES
+----------
+GIT_AUTHOR_IDENT::
+    The author of a piece of code.
+
+GIT_COMMITTER_IDENT::
+    The person who put a piece of code into git.
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+    The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+    The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+    The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+gitlink:git-commit-tree[1]
+gitlink:git-tag[1]
+gitlink:git-repo-config[1]
+
+Author
+------
+Written by Eric Biederman <ebiederm@xmission.com>
+
+Documentation
+--------------
+Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
new file mode 100644 (file)
index 0000000..7a6132b
--- /dev/null
@@ -0,0 +1,54 @@
+git-verify-pack(1)
+==================
+
+NAME
+----
+git-verify-pack - Validate packed git archive files
+
+
+SYNOPSIS
+--------
+'git-verify-pack' [-v] [--] <pack>.idx ...
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed git archive created with
+git-pack-objects command and verifies idx file and the
+corresponding pack file.
+
+OPTIONS
+-------
+<pack>.idx ...::
+       The idx files to verify.
+
+-v::
+       After verifying the pack, show list of objects contained
+       in the pack.
+\--::
+       Do not interpret any more arguments as options.
+
+OUTPUT FORMAT
+-------------
+When specifying the -v option the format used is:
+
+       SHA1 type size offset-in-packfile
+
+for objects that are not deltified in the pack, and
+
+       SHA1 type size offset-in-packfile depth base-SHA1
+
+for objects that are deltified.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
new file mode 100644 (file)
index 0000000..0f9bdb5
--- /dev/null
@@ -0,0 +1,32 @@
+git-verify-tag(1)
+=================
+
+NAME
+----
+git-verify-tag - Check the GPG signature of tag
+
+SYNOPSIS
+--------
+'git-verify-tag' <tag>
+
+DESCRIPTION
+-----------
+Validates the gpg signature created by git-tag.
+
+OPTIONS
+-------
+<tag>::
+       SHA1 identifier of a git tag object.
+
+Author
+------
+Written by Jan Harkes <jaharkes@cs.cmu.edu> and Eric W. Biederman <ebiederm@xmission.com>
+
+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-whatchanged.txt b/Documentation/git-whatchanged.txt
new file mode 100644 (file)
index 0000000..e8f21d0
--- /dev/null
@@ -0,0 +1,81 @@
+git-whatchanged(1)
+==================
+
+NAME
+----
+git-whatchanged - Show logs with difference each commit introduces
+
+
+SYNOPSIS
+--------
+'git-whatchanged' <option>...
+
+DESCRIPTION
+-----------
+Shows commit logs and diff output each commit introduces.  The
+command internally invokes 'git-rev-list' piped to
+'git-diff-tree', and takes command line options for both of
+these commands.
+
+This manual page describes only the most frequently used options.
+
+
+OPTIONS
+-------
+-p::
+       Show textual diffs, instead of the git internal diff
+       output format that is useful only to tell the changed
+       paths and their nature of changes.
+
+--max-count=<n>::
+       Limit output to <n> commits.
+
+<since>..<until>::
+       Limit output to between the two named commits (bottom
+       exclusive, top inclusive).
+
+-r::
+       Show git internal diff output, but for the whole tree,
+       not just the top level.
+
+--pretty=<format>::
+       Controls the output format for the commit logs.
+       <format> can be one of 'raw', 'medium', 'short', 'full',
+       and 'oneline'.
+
+-m::
+       By default, differences for merge commits are not shown.
+       With this flag, show differences to that commit from all
+       of its parents.
++
+However, it is not very useful in general, although it
+*is* useful on a file-by-file basis.
+
+Examples
+--------
+git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
+
+       Show as patches the commits since version 'v2.6.12' that changed
+       any file in the include/scsi or drivers/scsi subdirectories
+
+git-whatchanged --since="2 weeks ago" \-- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+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-write-tree.txt b/Documentation/git-write-tree.txt
new file mode 100644 (file)
index 0000000..77e12cb
--- /dev/null
@@ -0,0 +1,44 @@
+git-write-tree(1)
+=================
+
+NAME
+----
+git-write-tree - Creates a tree object from the current index
+
+
+SYNOPSIS
+--------
+'git-write-tree' [--missing-ok]
+
+DESCRIPTION
+-----------
+Creates a tree object using the current index.
+
+The index must be in a fully merged state.
+
+Conceptually, `git-write-tree` sync()s the current index contents
+into a set of tree files.
+In order to have that match what is actually in your directory right
+now, you need to have done a `git-update-index` phase before you did the
+`git-write-tree`.
+
+
+OPTIONS
+-------
+--missing-ok::
+       Normally `git-write-tree` ensures that the objects referenced by the
+       directory exist in the object database.  This option disables this
+       check.
+
+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.txt b/Documentation/git.txt
new file mode 100644 (file)
index 0000000..24ca55d
--- /dev/null
@@ -0,0 +1,632 @@
+git(7)
+======
+
+NAME
+----
+git - the stupid content tracker
+
+
+SYNOPSIS
+--------
+'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ARGS]
+
+DESCRIPTION
+-----------
+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.
+
+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].
+
+OPTIONS
+-------
+--version::
+       Prints the git suite version that the 'git' program came from.
+
+--help::
+       Prints the synopsis and a list of the most commonly used
+       commands.  If a git command is named this option will bring up
+       the man-page for that command. If the option '--all' or '-a' is
+       given then all available commands are printed.
+
+--exec-path::
+       Path to wherever your core git programs are installed.
+       This can also be controlled by setting the GIT_EXEC_PATH
+       environment variable. If no path is given 'git' will print
+       the current setting and then exit.
+
+
+FURTHER DOCUMENTATION
+---------------------
+
+See the references above to get started using git.  The following is
+probably more detail than necessary for a first-time user.
+
+The <<Discussion,Discussion>> section below and the
+link:core-tutorial.html[Core tutorial] both provide introductions to the
+underlying git architecture.
+
+See also the link:howto-index.html[howto] documents for some useful
+examples.
+
+GIT COMMANDS
+------------
+
+We divide git into high level ("porcelain") commands and low level
+("plumbing") commands.
+
+Low-level commands (plumbing)
+-----------------------------
+
+Although git includes its
+own porcelain layer, its low-level commands are sufficient to support
+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 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.
+
+
+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-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-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.
+
+In general, the interrogate commands do not touch the files in
+the working tree.
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-clone-pack[1]::
+       Clones a repository into the current repository (engine
+       for ssh and local transport).
+
+gitlink:git-fetch-pack[1]::
+       Updates from a remote repository (engine for ssh and
+       local transport).
+
+gitlink:git-http-fetch[1]::
+       Downloads a remote git repository via HTTP by walking
+       commit chain.
+
+gitlink:git-local-fetch[1]::
+       Duplicates another git repository on a local system by
+       walking commit chain.
+
+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.
+
+gitlink:git-send-pack[1]::
+       Pushes to a remote repository, intelligently.
+
+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-pack[1]::
+       Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
+       what are asked for.
+
+
+High-level commands (porcelain)
+-------------------------------
+
+We separate the porcelain commands into the main commands and some
+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-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: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-pull[1]::
+       Fetch from and merge with a remote repository.
+
+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.
+
+
+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-relink[1]::
+       Hardlink common objects in local repositories.
+
+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.
+
+
+Interrogators:
+
+gitlink:git-annotate[1]::
+       Annotate file lines with commit info.
+
+gitlink:git-blame[1]::
+       Blame file lines on commits.
+
+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-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.
+
+gitlink:git-request-pull[1]::
+       git-request-pull.
+
+gitlink:git-rev-parse[1]::
+       Pick out and massage parameters.
+
+gitlink:git-send-email[1]::
+       Send patch e-mails out of "format-patch --mbox" output.
+
+gitlink:git-symbolic-ref[1]::
+       Read and modify symbolic refs.
+
+gitlink:git-stripspace[1]::
+       Filter out empty lines.
+
+
+Commands not yet documented
+---------------------------
+
+gitlink:gitk[1]::
+       The gitk repository browser.
+
+
+Configuration Mechanism
+-----------------------
+
+Starting from 0.99.9 (actually mid 0.99.8.GIT), `.git/config` file
+is used to hold per-repository configuration options.  It is a
+simple text file modelled after `.ini` format familiar to some
+people.  Here is an example:
+
+------------
+#
+# A '#' or ';' character indicates a comment.
+#
+
+; core variables
+[core]
+       ; Don't trust file modes
+       filemode = false
+
+; user identity
+[user]
+       name = "Junio C Hamano"
+       email = "junkio@twinsun.com"
+
+------------
+
+Various commands read from the configuration file and adjust
+their operation accordingly.
+
+
+Identifier Terminology
+----------------------
+<object>::
+       Indicates the object name for any type of object.
+
+<blob>::
+       Indicates a blob object name.
+
+<tree>::
+       Indicates a tree object name.
+
+<commit>::
+       Indicates a commit object name.
+
+<tree-ish>::
+       Indicates a tree, commit or tag object name.  A
+       command that takes a <tree-ish> argument ultimately wants to
+       operate on a <tree> object but automatically dereferences
+       <commit> and <tag> objects that point at a <tree>.
+
+<type>::
+       Indicates that an object type is required.
+       Currently one of: `blob`, `tree`, `commit`, or `tag`.
+
+<file>::
+       Indicates a filename - almost always relative to the
+       root of the tree structure `GIT_INDEX_FILE` describes.
+
+Symbolic Identifiers
+--------------------
+Any git command accepting any <object> can also use the following
+symbolic notation:
+
+HEAD::
+       indicates the head of the current branch (i.e. the
+       contents of `$GIT_DIR/HEAD`).
+
+<tag>::
+       a valid tag 'name'
+       (i.e. the contents of `$GIT_DIR/refs/tags/<tag>`).
+
+<head>::
+       a valid head 'name'
+       (i.e. the contents of `$GIT_DIR/refs/heads/<head>`).
+
+
+File/Directory Structure
+------------------------
+
+Please see link:repository-layout.html[repository layout] document.
+
+Read link:hooks.html[hooks] for more details about each hook.
+
+Higher level SCMs may provide and manage additional information in the
+`$GIT_DIR`.
+
+
+Terminology
+-----------
+Please see link:glossary.html[glossary] document.
+
+
+Environment Variables
+---------------------
+Various git commands use the following environment variables:
+
+The git Repository
+~~~~~~~~~~~~~~~~~~
+These environment variables apply to 'all' core git commands. Nb: it
+is worth noting that they may be used/overridden by SCMS sitting above
+git so take care if using Cogito etc.
+
+'GIT_INDEX_FILE'::
+       This environment allows the specification of an alternate
+       index file. If not specified, the default of `$GIT_DIR/index`
+       is used.
+
+'GIT_OBJECT_DIRECTORY'::
+       If the object storage directory is specified via this
+       environment variable then the sha1 directories are created
+       underneath - otherwise the default `$GIT_DIR/objects`
+       directory is used.
+
+'GIT_ALTERNATE_OBJECT_DIRECTORIES'::
+       Due to the immutable nature of git objects, old objects can be
+       archived into shared, read-only directories. This variable
+       specifies a ":" separated list of git object directories which
+       can be used to search for git objects. New objects will not be
+       written to these directories.
+
+'GIT_DIR'::
+       If the 'GIT_DIR' environment variable is set then it
+       specifies a path to use instead of the default `.git`
+       for the base of the repository.
+
+git Commits
+~~~~~~~~~~~
+'GIT_AUTHOR_NAME'::
+'GIT_AUTHOR_EMAIL'::
+'GIT_AUTHOR_DATE'::
+'GIT_COMMITTER_NAME'::
+'GIT_COMMITTER_EMAIL'::
+       see gitlink:git-commit-tree[1]
+
+git Diffs
+~~~~~~~~~
+'GIT_DIFF_OPTS'::
+'GIT_EXTERNAL_DIFF'::
+       see the "generating patches" section in :
+       gitlink:git-diff-index[1];
+       gitlink:git-diff-files[1];
+       gitlink:git-diff-tree[1]
+
+Discussion[[Discussion]]
+------------------------
+include::README[]
+
+Authors
+-------
+* git's founding father is Linus Torvalds <torvalds@osdl.org>.
+* The current git nurse is Junio C Hamano <junkio@cox.net>.
+* The git potty was written by Andres Ericsson <ae@op5.se>.
+* General upbringing is handled by the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+The documentation for git suite was started by David Greaves
+<david@dgreaves.com>, and later enhanced greatly by the
+contributors on the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
new file mode 100644 (file)
index 0000000..cb482bf
--- /dev/null
@@ -0,0 +1,51 @@
+gitk(1)
+=======
+
+NAME
+----
+gitk - Some git command not yet documented.
+
+
+SYNOPSIS
+--------
+'gitk' [ --option ] <args>...
+
+DESCRIPTION
+-----------
+Does something not yet documented.
+
+
+OPTIONS
+-------
+--option::
+       Some option not yet documented.
+
+<args>...::
+       Some argument not yet documented.
+
+
+Examples
+--------
+gitk v2.6.12.. include/scsi drivers/scsi::
+
+       Show as the changes since version 'v2.6.12' that changed any
+       file in the include/scsi or drivers/scsi subdirectories
+
+gitk --since="2 weeks ago" \-- gitk::
+
+       Show the changes during the last two weeks to the file 'gitk'.
+       The "--" is necessary to avoid confusion with the *branch* named
+       'gitk'
+
+Author
+------
+Written by Paul Mackerras <paulus@samba.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt
new file mode 100644 (file)
index 0000000..39c90ad
--- /dev/null
@@ -0,0 +1,326 @@
+alternate object database::
+       Via the alternates mechanism, a repository can inherit part of its
+       object database from another object database, which is called
+       "alternate".
+
+bare repository::
+       A bare repository is normally an appropriately named
+       directory with a `.git` suffix that does not have a
+       locally checked-out copy of any of the files under revision
+       control.  That is, all of the `git` administrative and
+       control files that would normally be present in the
+       hidden `.git` sub-directory are directly present in
+       the `repository.git` directory instead, and no other files
+       are present and checked out.  Usually publishers of public
+       repositories make bare repositories available.
+
+blob object::
+       Untyped object, e.g. the contents of a file.
+
+branch::
+       A non-cyclical graph of revisions, i.e. the complete history of
+       a particular revision, which is called the branch head. The
+       branch heads are stored in `$GIT_DIR/refs/heads/`.
+
+cache::
+       Obsolete for: index.
+
+chain::
+       A list of objects, where each object in the list contains a
+       reference to its successor (for example, the successor of a commit
+       could be one of its parents).
+
+changeset::
+       BitKeeper/cvsps speak for "commit". Since git does not store
+       changes, but states, it really does not make sense to use
+       the term "changesets" with git.
+
+checkout::
+       The action of updating the working tree to a revision which was
+       stored in the object database.
+
+cherry-picking::
+       In SCM jargon, "cherry pick" means to choose a subset of
+       changes out of a series of changes (typically commits)
+       and record them as a new series of changes on top of
+       different codebase.  In GIT, this is performed by
+       "git cherry-pick" command to extract the change
+       introduced by an existing commit and to record it based
+       on the tip of the current branch as a new commit.
+
+clean::
+       A working tree is clean, if it corresponds to the revision
+       referenced by the current head.  Also see "dirty".
+
+commit::
+       As a verb: The action of storing the current state of the index in the
+       object database. The result is a revision.
+       As a noun: Short hand for commit object.
+
+commit object::
+       An object which contains the information about a particular
+       revision, such as parents, committer, author, date and the
+       tree object which corresponds to the top directory of the
+       stored revision.
+
+core git::
+       Fundamental data structures and utilities of git. Exposes only
+       limited source code management tools.
+
+DAG::
+       Directed acyclic graph. The commit objects form a directed acyclic
+       graph, because they have parents (directed), and the graph of commit
+       objects is acyclic (there is no chain which begins and ends with the
+       same object).
+
+dircache::
+       You are *waaaaay* behind.
+
+dirty::
+       A working tree is said to be dirty if it contains modifications
+       which have not been committed to the current branch.
+
+directory::
+       The list you get with "ls" :-)
+
+ent::
+       Favorite synonym to "tree-ish" by some total geeks. See
+       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
+       explanation.
+
+fast forward::
+       A fast-forward is a special type of merge where you have
+       a revision and you are "merging" another branch's changes
+       that happen to be a descendant of what you have.
+       In such these cases, you do not make a new merge commit but
+       instead just update to his revision. This will happen
+       frequently on a tracking branch of a remote repository.
+
+fetch::
+       Fetching a branch means to get the branch's head ref from a
+       remote repository, to find out which objects are missing from
+       the local object database, and to get them, too.
+
+file system::
+       Linus Torvalds originally designed git to be a user space file
+       system, i.e. the infrastructure to hold files and directories.
+       That ensured the efficiency and speed of git.
+
+git archive::
+       Synonym for repository (for arch people).
+
+hash::
+       In git's context, synonym to object name.
+
+head::
+       The top of a branch. It contains a ref to the corresponding
+       commit object.
+
+head ref::
+       A ref pointing to a head. Often, this is abbreviated to "head".
+       Head refs are stored in `$GIT_DIR/refs/heads/`.
+
+hook::
+       During the normal execution of several git commands,
+       call-outs are made to optional scripts that allow
+       a developer to add functionality or checking.
+       Typically, the hooks allow for a command to be pre-verified
+       and potentially aborted, and allow for a post-notification
+       after the operation is done.
+       The hook scripts are found in the `$GIT_DIR/hooks/` directory,
+       and are enabled by simply making them executable.
+
+index::
+       A collection of files with stat information, whose contents are
+       stored as objects. The index is a stored version of your working
+       tree. Truth be told, it can also contain a second, and even a third
+       version of a working tree, which are used when merging.
+
+index entry::
+       The information regarding a particular file, stored in the index.
+       An index entry can be unmerged, if a merge was started, but not
+       yet finished (i.e. if the index contains multiple versions of
+       that file).
+
+master::
+       The default development branch. Whenever you create a git
+       repository, a branch named "master" is created, and becomes
+       the active branch. In most cases, this contains the local
+       development, though that is purely conventional and not required.
+
+merge::
+       To merge branches means to try to accumulate the changes since a
+       common ancestor and apply them to the first branch. An automatic
+       merge uses heuristics to accomplish that. Evidently, an automatic
+       merge can fail.
+
+object::
+       The unit of storage in git. It is uniquely identified by
+       the SHA1 of its contents. Consequently, an object can not
+       be changed.
+
+object database::
+       Stores a set of "objects", and an individual object is identified
+       by its object name. The objects usually live in `$GIT_DIR/objects/`.
+
+object identifier::
+       Synonym for object name.
+
+object name::
+       The unique identifier of an object. The hash of the object's contents
+       using the Secure Hash Algorithm 1 and usually represented by the 40
+       character hexadecimal encoding of the hash of the object (possibly
+       followed by a white space).
+
+object type:
+       One of the identifiers "commit","tree","tag" and "blob" describing
+       the type of an object.
+
+octopus::
+       To merge more than two branches. Also denotes an intelligent
+       predator.
+
+origin::
+       The default upstream tracking branch. 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.
+
+pack::
+       A set of objects which have been compressed into one file (to save
+       space or to transmit them efficiently).
+
+pack index::
+       The list of identifiers, and other information, of the objects in a
+       pack, to assist in efficiently accessing the contents of a pack.
+
+parent::
+       A commit object contains a (possibly empty) list of the logical
+       predecessor(s) in the line of development, i.e. its parents.
+
+pickaxe::
+       The term pickaxe refers to an option to the diffcore routines
+       that help select changes that add or delete a given text string.
+       With the --pickaxe-all option, it can be used to view the
+       full changeset that introduced or removed, say, a particular
+       line of text.  See gitlink:git-diff[1].
+
+plumbing::
+       Cute name for core git.
+
+porcelain::
+       Cute name for programs and program suites depending on core git,
+       presenting a high level access to core git. Porcelains expose
+       more of a SCM interface than the plumbing.
+
+pull::
+       Pulling a branch means to fetch it and merge it.
+
+push::
+       Pushing a branch means to get the branch's head ref from a remote
+       repository, find out if it is an ancestor to the branch's local
+       head ref is a direct, and in that case, putting all objects, which
+       are reachable from the local head ref, and which are missing from
+       the remote repository, into the remote object database, and updating
+       the remote head ref. If the remote head is not an ancestor to the
+       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.
+
+rebase::
+       To clean a branch by starting from the head of the main line of
+       development ("master"), and reapply the (possibly cherry-picked)
+       changes from that branch.
+
+ref::
+       A 40-byte hex representation of a SHA1 or a name that denotes
+       a particular object. These may be stored in `$GIT_DIR/refs/`.
+
+refspec::
+       A refspec is used by fetch and push to describe the mapping
+       between remote ref and local ref.  They are combined with
+       a colon in the format <src>:<dst>, preceded by an optional
+       plus sign, +.  For example:
+       `git fetch $URL refs/heads/master:refs/heads/origin`
+       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
+       at $URL".   See also gitlink:git-push[1]
+
+repository::
+       A collection of refs together with an object database containing
+       all objects, which are reachable from the refs, possibly accompanied
+       by meta data from one or more porcelains. A repository can
+       share an object database with other repositories.
+
+resolve::
+       The action of fixing up manually what a failed automatic merge
+       left behind.
+
+revision::
+       A particular state of files and directories which was stored in
+       the object database. It is referenced by a commit object.
+
+rewind::
+       To throw away part of the development, i.e. to assign the head to
+       an earlier revision.
+
+SCM::
+       Source code management (tool).
+
+SHA1::
+       Synonym for object name.
+
+topic branch::
+       A regular git branch that is used by a developer to
+       identify a conceptual line of development.  Since branches
+       are very easy and inexpensive, it is often desirable to
+       have several small branches that each contain very well
+       defined concepts or small incremental yet related changes.
+
+tracking branch::
+       A regular git branch that is used to follow changes from
+       another repository.  A tracking branch should not contain
+       direct modifications or have local commits made to it.
+       A tracking branch can usually be identified as the
+       right-hand-side ref in a Pull: refspec.
+
+tree object::
+       An object containing a list of file names and modes along with refs
+       to the associated blob and/or tree objects. A tree is equivalent
+       to a directory.
+
+tree::
+       Either a working tree, or a tree object together with the
+       dependent blob and tree objects (i.e. a stored representation
+       of a working tree).
+
+tree-ish::
+       A ref pointing to either a commit object, a tree object, or a
+       tag object pointing to a tag or commit or tree object.
+
+tag object::
+       An object containing a ref pointing to another object, which can
+       contain a message just like a commit object. It can also
+       contain a (PGP) signature, in which case it is called a "signed
+       tag object".
+
+tag::
+       A ref pointing to a tag or commit object. In contrast to a head,
+       a tag is not changed by a commit. Tags (not tag objects) are
+       stored in `$GIT_DIR/refs/tags/`. A git tag has nothing to do with
+       a Lisp tag (which is called object type in git's context).
+       A tag is most typically used to mark a particular point in the
+       commit ancestry chain.
+
+unmerged index:
+       An index which contains unmerged index entries.
+
+working tree::
+       The set of files and directories currently being worked on,
+       i.e. you can work in your working tree without using git at all.
+
diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt
new file mode 100644 (file)
index 0000000..e3dde39
--- /dev/null
@@ -0,0 +1,160 @@
+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
+`hooks` directory of the new repository, but by default they are
+all disabled.  To enable a hook, make it executable with `chmod
++x`.
+
+This document describes the currently defined hooks.
+
+applypatch-msg
+--------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`.  It takes a single
+parameter, the name of the file that holds the proposed commit
+log message.  Exiting with non-zero status causes the
+'git-applypatch' to abort before applying the patch.
+
+The hook is allowed to edit the message file in place, and can
+be used to normalize the message into some project standard
+format (if the project has one). It can also be used to refuse
+the commit after inspecting the message file.
+
+The default applypatch-msg hook, when enabled, runs the
+commit-msg hook, if the latter is enabled.
+
+pre-applypatch
+--------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`.  It takes no parameter,
+and is invoked after the patch is applied, but before a commit
+is made.  Exiting with non-zero status causes the working tree
+after application of the patch not committed.
+
+It can be used to inspect the current working tree and refuse to
+make a commit if it does not pass certain test.
+
+The default pre-applypatch hook, when enabled, runs the
+pre-commit hook, if the latter is enabled.
+
+post-applypatch
+---------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`.  It takes no parameter,
+and is invoked after the patch is applied and a commit is made.
+
+This hook is meant primarily for notification, and cannot affect
+the outcome of `git-applypatch`.
+
+pre-commit
+----------
+
+This hook is invoked by `git-commit`, and can be bypassed
+with `\--no-verify` option.  It takes no parameter, and is
+invoked before obtaining the proposed commit log message and
+making a commit.  Exiting with non-zero status from this script
+causes the `git-commit` to abort.
+
+The default pre-commit hook, when enabled, catches introduction
+of lines with trailing whitespaces and aborts the commit when
+a such line is found.
+
+commit-msg
+----------
+
+This hook is invoked by `git-commit`, and can be bypassed
+with `\--no-verify` option.  It takes a single parameter, the
+name of the file that holds the proposed commit log message.
+Exiting with non-zero status causes the `git-commit` to
+abort.
+
+The hook is allowed to edit the message file in place, and can
+be used to normalize the message into some project standard
+format (if the project has one). It can also be used to refuse
+the commit after inspecting the message file.
+
+The default commit-msg hook, when enabled, detects duplicate
+Signed-off-by: lines, and aborts the commit when one is found.
+
+post-commit
+-----------
+
+This hook is invoked by `git-commit`.  It takes no
+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
+------
+
+This hook is invoked by `git-receive-pack` on the remote repository,
+which is happens when a `git push` is done on a local repository.
+Just before updating the ref on the remote repository, the update hook
+is invoked.  It's exit status determines the success or failure of
+the ref update.
+
+The hook executes once for each ref to be updated, and takes
+three parameters:
+    - the name of the ref being updated,
+    - the old object name stored in the ref,
+    - and the new objectname to be stored in the ref.
+
+A zero exit from the update hook allows the ref to be updated.
+Exiting with a non-zero status prevents `git-receive-pack`
+from updating the ref.
+
+This hook can be used to prevent 'forced' update on certain refs by
+making sure that the object name is a commit object that is a
+descendant of the commit object named by the old object name.
+That is, to enforce a "fast forward only" policy.
+
+It could also be used to log the old..new status.  However, it
+does not know the entire set of branches, so it would end up
+firing one e-mail per ref when used naively, though.
+
+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
+want to report something to the git-send-pack on the other end,
+you can redirect your output to your stderr.
+
+
+post-update
+-----------
+
+This hook is invoked by `git-receive-pack` on the remote repository,
+which is happens when a `git push` is done on a local repository.
+It executes on the remote repository once after all the refs have
+been updated.
+
+It takes a variable number of parameters, each of which is the
+name of ref that was actually updated.
+
+This hook is meant primarily for notification, and cannot affect
+the outcome of `git-receive-pack`.
+
+The post-update hook can tell what are the heads that were pushed,
+but it does not know what their original and updated values are,
+so it is a poor place to do log old..new.
+
+The default post-update hook, when enabled, runs
+`git-update-server-info` to keep the information used by dumb
+transports (e.g., http) up-to-date.  If you are publishing
+a git repository that is accessible via http, you should
+probably enable this hook.
+
+The standard output of this hook is sent to /dev/null; if you
+want to report something to the git-send-pack on the other end,
+you can redirect your output to your stderr.
diff --git a/Documentation/howto-index.sh b/Documentation/howto-index.sh
new file mode 100755 (executable)
index 0000000..34aa30c
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+cat <<\EOF
+GIT Howto Index
+===============
+
+Here is a collection of mailing list postings made by various
+people describing how they use git in their workflow.
+
+EOF
+
+for txt
+do
+       title=`expr "$txt" : '.*/\(.*\)\.txt$'`
+       from=`sed -ne '
+       /^$/q
+       /^From:[        ]/{
+               s///
+               s/^[    ]*//
+               s/[     ]*$//
+               s/^/by /
+               p
+       }
+       ' "$txt"`
+
+       abstract=`sed -ne '
+       /^Abstract:[    ]/{
+               s/^[^   ]*//
+               x
+               s/.*//
+               x
+               : again
+               /^[     ]/{
+                       s/^[    ]*//
+                       H
+                       n
+                       b again
+               }
+               x
+               p
+               q
+       }' "$txt"`
+
+       if grep 'Content-type: text/asciidoc' >/dev/null $txt
+       then
+               file=`expr "$txt" : '\(.*\)\.txt$'`.html
+       else
+               file="$txt"
+       fi
+
+       echo "* link:$file[$title] $from
+$abstract
+
+"
+
+done
diff --git a/Documentation/howto/isolate-bugs-with-bisect.txt b/Documentation/howto/isolate-bugs-with-bisect.txt
new file mode 100644 (file)
index 0000000..edbcd4c
--- /dev/null
@@ -0,0 +1,65 @@
+From:  Linus Torvalds <torvalds () osdl ! org>
+To:    git@vger.kernel.org
+Date:  2005-11-08 1:31:34
+Subject: Real-life kernel debugging scenario
+Abstract: Short-n-sweet, Linus tells us how to leverage `git-bisect` to perform
+       bug isolation on a repository where "good" and "bad" revisions are known
+       in order to identify a suspect commit.
+
+
+How To Use git-bisect To Isolate a Bogus Commit
+===============================================
+
+The way to use "git bisect" couldn't be easier.
+
+Figure out what the oldest bad state you know about is (that's usually the 
+head of "master", since that's what you just tried to boot and failed at). 
+Also, figure out the most recent known-good commit (usually the _previous_ 
+kernel you ran: and if you've only done a single "pull" in between, it 
+will be ORIG_HEAD).
+
+Then do
+
+       git bisect start
+       git bisect bad master           <- mark "master" as the bad state
+       git bisect good ORIG_HEAD       <- mark ORIG_HEAD as good (or
+                                          whatever other known-good 
+                                          thing you booted last)
+
+and at this point "git bisect" will churn for a while, and tell you what 
+the mid-point between those two commits are, and check that state out as 
+the head of the bew "bisect" branch.
+
+Compile and reboot.
+
+If it's good, just do
+
+       git bisect good         <- mark current head as good
+
+otherwise, reboot into a good kernel instead, and do (surprise surprise, 
+git really is very intuitive):
+
+       git bisect bad          <- mark current head as bad
+
+and whatever you do, git will select a new half-way point. Do this for a 
+while, until git tells you exactly which commit was the first bad commit. 
+That's your culprit.
+
+It really works wonderfully well, except for the case where there was 
+_another_ commit that broke something in between, like introduced some 
+stupid compile error. In that case you should not mark that commit good or 
+bad: you should try to find another commit close-by, and do a "git reset 
+--hard <newcommit>" to try out _that_ commit instead, and then test that 
+instead (and mark it good or bad).
+
+You can do "git bisect visualize" while you do all this to see what's 
+going on by starting up gitk on the bisection range.
+
+Finally, once you've figured out exactly which commit was bad, you can 
+then go back to the master branch, and try reverting just that commit:
+
+       git checkout master
+       git revert <bad-commit-id>
+
+to verify that the top-of-kernel works with that single commit reverted.
+
diff --git a/Documentation/howto/make-dist.txt b/Documentation/howto/make-dist.txt
new file mode 100644 (file)
index 0000000..00e330b
--- /dev/null
@@ -0,0 +1,52 @@
+Date:   Fri, 12 Aug 2005 22:39:48 -0700 (PDT)
+From: Linus Torvalds <torvalds@osdl.org>
+To: Dave Jones <davej@redhat.com>
+cc: git@vger.kernel.org
+Subject: Re: Fwd: Re: git checkout -f branch doesn't remove extra files
+Abstract: In this article, Linus talks about building a tarball,
+ incremental patch, and ChangeLog, given a base release and two
+ rc releases, following the convention of giving the patch from
+ the base release and the latest rc, with ChangeLog between the
+ last rc and the latest rc.
+
+On Sat, 13 Aug 2005, Dave Jones wrote:
+>
+>  > Git actually has a _lot_ of nifty tools. I didn't realize that people
+>  > didn't know about such basic stuff as "git-tar-tree" and "git-ls-files".
+>
+> Maybe its because things are moving so fast :)  Or maybe I just wasn't
+> paying attention on that day. (I even read the git changes via RSS,
+> so I should have no excuse).
+
+Well, git-tar-tree has been there since late April - it's actually one of
+those really early commands. I'm pretty sure the RSS feed came later ;)
+
+I use it all the time in doing releases, it's a lot faster than creating a
+tar tree by reading the filesystem (even if you don't have to check things
+out). A hidden pearl.
+
+This is my crappy "release-script":
+
+        [torvalds@g5 ~]$ cat bin/release-script
+        #!/bin/sh
+        stable="$1"
+        last="$2"
+        new="$3"
+        echo "# git-tag v$new"
+        echo "git-tar-tree v$new linux-$new | gzip -9 > ../linux-$new.tar.gz"
+        echo "git-diff-tree -p v$stable v$new | gzip -9 > ../patch-$new.gz"
+        echo "git-rev-list --pretty v$new ^v$last > ../ChangeLog-$new"
+        echo "git-rev-list --pretty=short v$new ^v$last | git-shortlog > ../ShortLog"
+        echo "git-diff-tree -p v$last v$new | git-apply --stat > ../diffstat-$new"
+
+and when I want to do a new kernel release I literally first tag it, and
+then do
+
+        release-script 2.6.12 2.6.13-rc6 2.6.13-rc7
+
+and check that things look sane, and then just cut-and-paste the commands.
+
+Yeah, it's stupid.
+
+                Linus
+
diff --git a/Documentation/howto/rebase-and-edit.txt b/Documentation/howto/rebase-and-edit.txt
new file mode 100644 (file)
index 0000000..646c55c
--- /dev/null
@@ -0,0 +1,81 @@
+Date:  Sat, 13 Aug 2005 22:16:02 -0700 (PDT)
+From:  Linus Torvalds <torvalds@osdl.org>
+To:    Steve French <smfrench@austin.rr.com>
+cc:    git@vger.kernel.org
+Subject: Re: sending changesets from the middle of a git tree
+Abstract: In this article, Linus demonstrates how a broken commit
+ in a sequence of commits can be removed by rewinding the head and
+ reapplying selected changes.
+
+On Sat, 13 Aug 2005, Linus Torvalds wrote:
+
+> That's correct. Same things apply: you can move a patch over, and create a 
+> new one with a modified comment, but basically the _old_ commit will be 
+> immutable.
+
+Let me clarify.
+
+You can entirely _drop_ old branches, so commits may be immutable, but
+nothing forces you to keep them. Of course, when you drop a commit, you'll 
+always end up dropping all the commits that depended on it, and if you 
+actually got somebody else to pull that commit you can't drop it from 
+_their_ repository, but undoing things is not impossible.
+
+For example, let's say that you've made a mess of things: you've committed
+three commits "old->a->b->c", and you notice that "a" was broken, but you
+want to save "b" and "c". What you can do is
+
+       # Create a branch "broken" that is the current code
+       # for reference
+       git branch broken
+
+       # Reset the main branch to three parents back: this 
+       # effectively undoes the three top commits
+       git reset HEAD^^^
+       git checkout -f
+
+       # Check the result visually to make sure you know what's
+       # going on
+       gitk --all
+
+       # Re-apply the two top ones from "broken"
+       #
+       # First "parent of broken" (aka b):
+       git-diff-tree -p broken^ | git-apply --index
+       git commit --reedit=broken^
+
+       # Then "top of broken" (aka c):
+       git-diff-tree -p broken | git-apply --index
+       git commit --reedit=broken
+
+and you've now re-applied (and possibly edited the comments) the two
+commits b/c, and commit "a" is basically gone (it still exists in the
+"broken" branch, of course).
+
+Finally, check out the end result again:
+
+       # Look at the new commit history
+       gitk --all
+
+to see that everything looks sensible.
+
+And then, you can just remove the broken branch if you decide you really 
+don't want it:
+
+       # remove 'broken' branch
+       git branch -d broken
+
+       # Prune old objects if you're really really sure
+       git prune
+
+And yeah, I'm sure there are other ways of doing this. And as usual, the 
+above is totally untested, and I just wrote it down in this email, so if 
+I've done something wrong, you'll have to figure it out on your own ;)
+
+                       Linus
+-
+To unsubscribe from this list: send the line "unsubscribe git" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
new file mode 100644 (file)
index 0000000..c2d4a91
--- /dev/null
@@ -0,0 +1,165 @@
+From:  Junio C Hamano <junkio@cox.net>
+To:    git@vger.kernel.org
+Cc:    Petr Baudis <pasky@suse.cz>, Linus Torvalds <torvalds@osdl.org>
+Subject: Re: sending changesets from the middle of a git tree
+Date:  Sun, 14 Aug 2005 18:37:39 -0700
+Abstract: In this article, JC talks about how he rebases the
+ public "pu" branch using the core GIT tools when he updates
+ the "master" branch, and how "rebase" works.  Also discussed
+ is how this applies to individual developers who sends patches
+ upstream.
+
+Petr Baudis <pasky@suse.cz> writes:
+
+> Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter
+> where Junio C Hamano <junkio@cox.net> told me that...
+>> Linus Torvalds <torvalds@osdl.org> writes:
+>> 
+>> > Junio, maybe you want to talk about how you move patches from your "pu" 
+>> > branch to the real branches.
+>> 
+> Actually, wouldn't this be also precisely for what StGIT is intended to?
+
+Exactly my feeling.  I was sort of waiting for Catalin to speak
+up.  With its basing philosophical ancestry on quilt, this is
+the kind of task StGIT is designed to do.
+
+I just have done a simpler one, this time using only the core
+GIT tools.
+
+I had a handful commits that were ahead of master in pu, and I
+wanted to add some documentation bypassing my usual habit of
+placing new things in pu first.  At the beginning, the commit
+ancestry graph looked like this:
+
+                             *"pu" head
+    master --> #1 --> #2 --> #3
+
+So I started from master, made a bunch of edits, and committed:
+
+    $ git checkout master
+    $ cd Documentation; ed git.txt ...
+    $ cd ..; git add Documentation/*.txt
+    $ git commit -s
+
+After the commit, the ancestry graph would look like this:
+
+                              *"pu" head
+    master^ --> #1 --> #2 --> #3
+          \
+            \---> master
+
+The old master is now master^ (the first parent of the master).
+The new master commit holds my documentation updates.
+
+Now I have to deal with "pu" branch.
+
+This is the kind of situation I used to have all the time when
+Linus was the maintainer and I was a contributor, when you look
+at "master" branch being the "maintainer" branch, and "pu"
+branch being the "contributor" branch.  Your work started at the
+tip of the "maintainer" branch some time ago, you made a lot of
+progress in the meantime, and now the maintainer branch has some
+other commits you do not have yet.  And "git rebase" was written
+with the explicit purpose of helping to maintain branches like
+"pu".  You _could_ merge master to pu and keep going, but if you
+eventually want to cherrypick and merge some but not necessarily
+all changes back to the master branch, it often makes later
+operations for _you_ easier if you rebase (i.e. carry forward
+your changes) "pu" rather than merge.  So I ran "git rebase":
+
+    $ git checkout pu
+    $ git rebase master pu
+
+What this does is to pick all the commits since the current
+branch (note that I now am on "pu" branch) forked from the
+master branch, and forward port these changes.
+
+    master^ --> #1 --> #2 --> #3
+          \                                  *"pu" head
+            \---> master --> #1' --> #2' --> #3'
+
+The diff between master^ and #1 is applied to master and
+committed to create #1' commit with the commit information (log,
+author and date) taken from commit #1.  On top of that #2' and #3'
+commits are made similarly out of #2 and #3 commits.
+
+Old #3 is not recorded in any of the .git/refs/heads/ file
+anymore, so after doing this you will have dangling commit if
+you ran fsck-cache, which is normal.  After testing "pu", you
+can run "git prune" to get rid of those original three commits.
+
+While I am talking about "git rebase", I should talk about how
+to do cherrypicking using only the core GIT tools.
+
+Let's go back to the earlier picture, with different labels.
+
+You, as an individual developer, cloned upstream repository and
+made a couple of commits on top of it.
+
+                              *your "master" head
+   upstream --> #1 --> #2 --> #3
+
+You would want changes #2 and #3 incorporated in the upstream,
+while you feel that #1 may need further improvements.  So you
+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
+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
+MUA would do as long as it does not corrupt whitespaces in the
+patch.
+
+Then you would wait, and you find out that the upstream picked
+up your changes, along with other changes.
+
+   where                      *your "master" head
+  upstream --> #1 --> #2 --> #3
+    used   \ 
+   to be     \--> #A --> #2' --> #3' --> #B --> #C
+                                                *upstream head
+
+The two commits #2' and #3' in the above picture record the same
+changes your e-mail submission for #2 and #3 contained, but
+probably with the new sign-off line added by the upsteam
+maintainer and definitely with different committer and ancestry
+information, they are different objects from #2 and #3 commits.
+
+You fetch from upstream, but not merge.
+
+    $ git fetch upstream
+
+This leaves the updated upstream head in .git/FETCH_HEAD but
+does not touch your .git/HEAD nor .git/refs/heads/master.  
+You run "git rebase" now.
+
+    $ git rebase FETCH_HEAD master
+
+Earlier, I said that rebase applies all the commits from your
+branch on top of the upstream head.  Well, I lied.  "git rebase"
+is a bit smarter than that and notices that #2 and #3 need not
+be applied, so it only applies #1.  The commit ancestry graph
+becomes something like this:
+
+   where                     *your old "master" head
+  upstream --> #1 --> #2 --> #3
+    used   \                      your new "master" head*
+   to be     \--> #A --> #2' --> #3' --> #B --> #C --> #1'
+                                                *upstream
+                                                head
+
+Again, "git prune" would discard the disused commits #1-#3 and
+you continue on starting from the new "master" head, which is
+the #1' commit.
+
+-jc
+
+-
+To unsubscribe from this list: send the line "unsubscribe git" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at  http://vger.kernel.org/majordomo-info.html
+
+
diff --git a/Documentation/howto/rebuild-from-update-hook.txt b/Documentation/howto/rebuild-from-update-hook.txt
new file mode 100644 (file)
index 0000000..02621b5
--- /dev/null
@@ -0,0 +1,87 @@
+Subject: [HOWTO] Using post-update hook
+Message-ID: <7vy86o6usx.fsf@assigned-by-dhcp.cox.net>
+From: Junio C Hamano <junkio@cox.net>
+Date: Fri, 26 Aug 2005 18:19:10 -0700
+Abstract: In this how-to article, JC talks about how he
+ uses the post-update hook to automate git documentation page
+ shown at http://www.kernel.org/pub/software/scm/git/docs/.
+
+The pages under http://www.kernel.org/pub/software/scm/git/docs/
+are built from Documentation/ directory of the git.git project
+and needed to be kept up-to-date.  The www.kernel.org/ servers
+are mirrored and I was told that the origin of the mirror is on
+the machine $some.kernel.org, on which I was given an account
+when I took over git maintainership from Linus.
+
+The directories relevant to this how-to are these two:
+
+    /pub/scm/git/git.git/      The public git repository.
+    /pub/software/scm/git/docs/        The HTML documentation page.
+
+So I made a repository to generate the documentation under my
+home directory over there.
+
+    $ cd
+    $ mkdir doc-git && cd doc-git
+    $ git clone /pub/scm/git/git.git/ docgen
+
+What needs to happen is to update the $HOME/doc-git/docgen/
+working tree, build HTML docs there and install the result in
+/pub/software/scm/git/docs/ directory.  So I wrote a little
+script:
+
+    $ cat >dododoc.sh <<\EOF
+    #!/bin/sh
+    cd $HOME/doc-git/docgen || exit
+
+    unset GIT_DIR
+
+    git pull /pub/scm/git/git.git/ master &&
+    cd Documentation &&
+    make install-webdoc
+    EOF
+
+Initially I used to run this by hand whenever I push into the
+public git repository.  Then I did a cron job that ran twice a
+day.  The current round uses the post-update hook mechanism,
+like this:
+
+    $ cat >/pub/scm/git/git.git/hooks/post-update <<\EOF
+    #!/bin/sh
+    #
+    # An example hook script to prepare a packed repository for use over
+    # dumb transports.
+    #
+    # To enable this hook, make this file executable by "chmod +x post-update".
+
+    case " $* " in
+    *' refs/heads/master '*)
+            echo $HOME/doc-git/dododoc.sh | at now
+            ;;
+    esac
+    exec git-update-server-info
+    EOF
+    $ chmod +x /pub/scm/git/git.git/hooks/post-update
+
+There are four things worth mentioning:
+
+ - The update-hook is run after the repository accepts a "git
+   push", under my user privilege.  It is given the full names
+   of refs that have been updated as arguments.  My post-update
+   runs the dododoc.sh script only when the master head is
+   updated.
+
+ - When update-hook is run, GIT_DIR is set to '.' by the calling
+   receive-pack.  This is inherited by the dododoc.sh run via
+   the "at" command, and needs to be unset; otherwise, "git
+   pull" it does into $HOME/doc-git/docgen/ repository would not
+   work correctly.
+
+ - The stdout of update hook script is not connected to git
+   push; I run the heavy part of the command inside "at", to
+   receive the execution report via e-mail.
+
+ - This is still crude and does not protect against simultaneous
+   make invocations stomping on each other.  I would need to add
+   some locking mechanism for this.
+
diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt
new file mode 100644 (file)
index 0000000..d10476b
--- /dev/null
@@ -0,0 +1,200 @@
+From: Junio C Hamano <junkio@cox.net>
+To: git@vger.kernel.org
+Subject: [HOWTO] Reverting an existing commit
+Abstract: In this article, JC gives a small real-life example of using
+ 'git revert' command, and using a temporary branch and tag for safety
+ and easier sanity checking.
+Date: Mon, 29 Aug 2005 21:39:02 -0700
+Content-type: text/asciidoc
+Message-ID: <7voe7g3uop.fsf@assigned-by-dhcp.cox.net>
+
+Reverting an existing commit
+============================
+
+One of the changes I pulled into the 'master' branch turns out to
+break building GIT with GCC 2.95.  While they were well intentioned
+portability fixes, keeping things working with gcc-2.95 was also
+important.  Here is what I did to revert the change in the 'master'
+branch and to adjust the 'pu' branch, using core GIT tools and
+barebone Porcelain.
+
+First, prepare a throw-away branch in case I screw things up.
+
+------------------------------------------------
+$ git checkout -b revert-c99 master
+------------------------------------------------
+
+Now I am on the 'revert-c99' branch.  Let's figure out which commit to
+revert.  I happen to know that the top of the 'master' branch is a
+merge, and its second parent (i.e. foreign commit I merged from) has
+the change I would want to undo.  Further I happen to know that that
+merge introduced 5 commits or so:
+
+------------------------------------------------
+$ git show-branch --more=4 master master^2 | head
+* [master] Merge refs/heads/portable from http://www.cs.berkeley....
+ ! [master^2] Replace C99 array initializers with code.
+--
+-  [master] Merge refs/heads/portable from http://www.cs.berkeley....
+*+ [master^2] Replace C99 array initializers with code.
+*+ [master^2~1] Replace unsetenv() and setenv() with older putenv().
+*+ [master^2~2] Include sys/time.h in daemon.c.
+*+ [master^2~3] Fix ?: statements.
+*+ [master^2~4] Replace zero-length array decls with [].
+*  [master~1] tutorial note about git branch
+------------------------------------------------
+
+The '--more=4' above means "after we reach the merge base of refs,
+show until we display four more common commits".  That last commit
+would have been where the "portable" branch was forked from the main
+git.git repository, so this would show everything on both branches
+since then.  I just limited the output to the first handful using
+'head'.
+
+Now I know 'master^2~4' (pronounce it as "find the second parent of
+the 'master', and then go four generations back following the first
+parent") is the one I would want to revert.  Since I also want to say
+why I am reverting it, the '-n' flag is given to 'git revert'.  This
+prevents it from actually making a commit, and instead 'git revert'
+leaves the commit log message it wanted to use in '.msg' file:
+
+------------------------------------------------
+$ git revert -n master^2~4
+$ cat .msg
+Revert "Replace zero-length array decls with []."
+
+This reverts 6c5f9baa3bc0d63e141e0afc23110205379905a4 commit.
+$ git diff HEAD ;# to make sure what we are reverting makes sense.
+$ make CC=gcc-2.95 clean test ;# make sure it fixed the breakage.
+$ make clean test ;# make sure it did not cause other breakage.
+------------------------------------------------
+
+The reverted change makes sense (from reading the 'diff' output), does
+fix the problem (from 'make CC=gcc-2.95' test), and does not cause new
+breakage (from the last 'make test').  I'm ready to commit:
+
+------------------------------------------------
+$ git commit -a -s ;# read .msg into the log,
+                    # and explain why I am reverting.
+------------------------------------------------
+
+I could have screwed up in any of the above steps, but in the worst
+case I could just have done 'git checkout master' to start over.
+Fortunately I did not have to; what I have in the current branch
+'revert-c99' is what I want.  So merge that back into 'master':
+
+------------------------------------------------
+$ git checkout master
+$ git resolve master revert-c99 fast ;# this should be a fast forward
+Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
+ cache.h        |    8 ++++----
+ commit.c       |    2 +-
+ ls-files.c     |    2 +-
+ receive-pack.c |    2 +-
+ server-info.c  |    2 +-
+ 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:
+
+------------------------------------------------
+$ git diff master..revert-c99
+------------------------------------------------
+
+says nothing.
+
+Then we rebase the 'pu' branch as usual.
+
+------------------------------------------------
+$ git checkout pu
+$ git tag pu-anchor pu
+$ git rebase master
+* Applying: Redo "revert" using three-way merge machinery.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: Remove git-apply-patch-script.
+First trying simple merge strategy to cherry-pick.
+Simple cherry-pick fails; trying Automatic cherry-pick.
+Removing Documentation/git-apply-patch-script.txt
+Removing git-apply-patch-script
+Finished one cherry-pick.
+* Applying: Document "git cherry-pick" and "git revert"
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: mailinfo and applymbox updates
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: Show commits in topo order and name all commits.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: More documentation updates.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+------------------------------------------------
+
+The temporary tag 'pu-anchor' is me just being careful, in case 'git
+rebase' screws up.  After this, I can do these for sanity check:
+
+------------------------------------------------
+$ git diff pu-anchor..pu ;# make sure we got the master fix.
+$ make CC=gcc-2.95 clean test ;# make sure it fixed the breakage.
+$ make clean test ;# make sure it did not cause other breakage.
+------------------------------------------------
+
+Everything is in the good order.  I do not need the temporary branch
+nor tag anymore, so remove them:
+
+------------------------------------------------
+$ rm -f .git/refs/tags/pu-anchor 
+$ git branch -d revert-c99
+------------------------------------------------
+
+It was an emergency fix, so we might as well merge it into the
+'release candidate' branch, although I expect the next release would
+be some days off:
+
+------------------------------------------------
+$ git checkout rc
+$ git pull . master
+Packing 0 objects
+Unpacking 0 objects
+
+* committish: e3a693c...       refs/heads/master from .
+Trying to merge e3a693c... into 8c1f5f0... using 10d781b...
+Committed merge 7fb9b7262a1d1e0a47bbfdcbbcf50ce0635d3f8f
+ cache.h        |    8 ++++----
+ commit.c       |    2 +-
+ ls-files.c     |    2 +-
+ receive-pack.c |    2 +-
+ server-info.c  |    2 +-
+ 5 files changed, 8 insertions(+), 8 deletions(-)
+------------------------------------------------
+
+And the final repository status looks like this:
+
+------------------------------------------------
+$ git show-branch --more=1 master pu rc
+! [master] Revert "Replace zero-length array decls with []."
+ ! [pu] git-repack: Add option to repack all objects.
+  * [rc] Merge refs/heads/master from .
+---
+ +  [pu] git-repack: Add option to repack all objects.
+ +  [pu~1] More documentation updates.
+ +  [pu~2] Show commits in topo order and name all commits.
+ +  [pu~3] mailinfo and applymbox updates
+ +  [pu~4] Document "git cherry-pick" and "git revert"
+ +  [pu~5] Remove git-apply-patch-script.
+ +  [pu~6] Redo "revert" using three-way merge machinery.
+  - [rc] Merge refs/heads/master from .
+++* [master] Revert "Replace zero-length array decls with []."
+  - [rc~1] Merge refs/heads/master from .
+... [master~1] Merge refs/heads/portable from http://www.cs.berkeley....
+------------------------------------------------
diff --git a/Documentation/howto/separating-topic-branches.txt b/Documentation/howto/separating-topic-branches.txt
new file mode 100644 (file)
index 0000000..090e2c9
--- /dev/null
@@ -0,0 +1,91 @@
+From: Junio C Hamano <junkio@cox.net>
+Subject: Separating topic branches
+Abstract: In this article, JC describes how to separate topic branches.
+
+This text was originally a footnote to a discussion about the
+behaviour of the git diff commands.
+
+Often I find myself doing that [running diff against something other
+than HEAD] while rewriting messy development history.  For example, I
+start doing some work without knowing exactly where it leads, and end
+up with a history like this:
+
+            "master"
+        o---o
+             \                    "topic" 
+              o---o---o---o---o---o
+
+At this point, "topic" contains something I know I want, but it
+contains two concepts that turned out to be completely independent.
+And often, one topic component is larger than the other.  It may
+contain more than two topics.
+
+In order to rewrite this mess to be more manageable, I would first do
+"diff master..topic", to extract the changes into a single patch, start
+picking pieces from it to get logically self-contained units, and
+start building on top of "master":
+
+        $ git diff master..topic >P.diff
+        $ git checkout -b topicA master
+        ... pick and apply pieces from P.diff to build
+        ... commits on topicA branch.
+                      
+              o---o---o
+             /        "topicA"
+        o---o"master"
+             \                    "topic" 
+              o---o---o---o---o---o
+
+Before doing each commit on "topicA" HEAD, I run "diff HEAD"
+before update-index the affected paths, or "diff --cached HEAD"
+after.  Also I would run "diff --cached master" to make sure
+that the changes are only the ones related to "topicA".  Usually
+I do this for smaller topics first.
+
+After that, I'd do the remainder of the original "topic", but
+for that, I do not start from the patchfile I extracted by
+comparing "master" and "topic" I used initially.  Still on
+"topicA", I extract "diff topic", and use it to rebuild the
+other topic:
+
+        $ git diff -R topic >P.diff ;# --cached also would work fine
+        $ git checkout -b topicB master
+        ... pick and apply pieces from P.diff to build
+        ... commits on topicB branch.
+
+                                "topicB"
+               o---o---o---o---o
+              /
+             /o---o---o
+            |/        "topicA"
+        o---o"master"
+             \                    "topic" 
+              o---o---o---o---o---o
+
+After I am done, I'd try a pretend-merge between "topicA" and
+"topicB" in order to make sure I have not missed anything:
+
+        $ git pull . topicA ;# merge it into current "topicB"
+        $ git diff topic
+                                "topicB"
+               o---o---o---o---o---* (pretend merge)
+              /                   /
+             /o---o---o----------'
+            |/        "topicA"
+        o---o"master"
+             \                    "topic" 
+              o---o---o---o---o---o
+
+The last diff better not to show anything other than cleanups
+for crufts.  Then I can finally clean things up:
+
+        $ git branch -D topic
+        $ git reset --hard HEAD^ ;# nuke pretend merge
+
+                                "topicB"
+               o---o---o---o---o
+              / 
+             /o---o---o
+            |/        "topicA"
+        o---o"master"
+
diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt
new file mode 100644 (file)
index 0000000..3a33696
--- /dev/null
@@ -0,0 +1,172 @@
+From: Junio C Hamano <junkio@cox.net> and Carl Baldwin <cnb@fc.hp.com>
+Subject: control access to branches.
+Date: Thu, 17 Nov 2005 23:55:32 -0800
+Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
+Abstract: An example hooks/update script is presented to
+ implement repository maintenance policies, such as who can push
+ into which branch and who can make a tag.
+
+When your developer runs git-push into the repository,
+git-receive-pack is run (either locally or over ssh) as that
+developer, so is hooks/update script.  Quoting from the relevant
+section of the documentation:
+
+    Before each ref is updated, if $GIT_DIR/hooks/update file exists
+    and executable, it is called with three parameters:
+
+           $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+    The refname parameter is relative to $GIT_DIR; e.g. for the
+    master head this is "refs/heads/master".  Two sha1 are the
+    object names for the refname before and after the update.  Note
+    that the hook is called before the refname is updated, so either
+    sha1-old is 0{40} (meaning there is no such ref yet), or it
+    should match what is recorded in refname.
+
+So if your policy is (1) always require fast-forward push
+(i.e. never allow "git-push repo +branch:branch"), (2) you
+have a list of users allowed to update each branch, and (3) you
+do not let tags to be overwritten, then you can use something
+like this as your hooks/update script.
+
+[jc: editorial note.  This is a much improved version by Carl
+since I posted the original outline]
+
+-- >8 -- beginning of script -- >8 --
+
+#!/bin/bash
+
+umask 002
+
+# If you are having trouble with this access control hook script
+# you can try setting this to true.  It will tell you exactly
+# why a user is being allowed/denied access.
+
+verbose=false
+
+# Default shell globbing messes things up downstream
+GLOBIGNORE=*
+
+function grant {
+  $verbose && echo >&2 "-Grant-                $1"
+  echo grant
+  exit 0
+}
+
+function deny {
+  $verbose && echo >&2 "-Deny-         $1"
+  echo deny
+  exit 1
+}
+
+function info {
+  $verbose && echo >&2 "-Info-         $1"
+}
+
+# Implement generic branch and tag policies.
+# - Tags should not be updated once created.
+# - Branches should only be fast-forwarded.
+case "$1" in
+  refs/tags/*)
+    [ -f "$GIT_DIR/$1" ] &&
+    deny >/dev/null "You can't overwrite an existing tag"
+    ;;
+  refs/heads/*)
+    # No rebasing or rewinding
+    if expr "$2" : '0*$' >/dev/null; then
+      info "The branch '$1' is new..."
+    else
+      # updating -- make sure it is a fast forward
+      mb=$(git-merge-base "$2" "$3")
+      case "$mb,$2" in
+        "$2,$mb") info "Update is fast-forward" ;;
+        *)        deny >/dev/null  "This is not a fast-forward update." ;;
+      esac
+    fi
+    ;;
+  *)
+    deny >/dev/null \
+    "Branch is not under refs/heads or refs/tags.  What are you trying to do?"
+    ;;
+esac
+
+# Implement per-branch controls based on username
+allowed_users_file=$GIT_DIR/info/allowed-users
+username=$(id -u -n)
+info "The user is: '$username'"
+
+if [ -f "$allowed_users_file" ]; then
+  rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
+    while read head_pattern user_patterns; do
+      matchlen=$(expr "$1" : "$head_pattern")
+      if [ "$matchlen" == "${#1}" ]; then
+        info "Found matching head pattern: '$head_pattern'"
+        for user_pattern in $user_patterns; do
+          info "Checking user: '$username' against pattern: '$user_pattern'"
+          matchlen=$(expr "$username" : "$user_pattern")
+          if [ "$matchlen" == "${#username}" ]; then
+            grant "Allowing user: '$username' with pattern: '$user_pattern'"
+          fi
+        done
+        deny "The user is not in the access list for this branch"
+      fi
+    done
+  )
+  case "$rc" in
+    grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
+    deny)  deny  >/dev/null "Denying  access based on $allowed_users_file" ;;
+    *) ;;
+  esac
+fi
+
+allowed_groups_file=$GIT_DIR/info/allowed-groups
+groups=$(id -G -n)
+info "The user belongs to the following groups:"
+info "'$groups'"
+
+if [ -f "$allowed_groups_file" ]; then
+  rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
+    while read head_pattern group_patterns; do
+      matchlen=$(expr "$1" : "$head_pattern")
+      if [ "$matchlen" == "${#1}" ]; then
+        info "Found matching head pattern: '$head_pattern'"
+        for group_pattern in $group_patterns; do
+          for groupname in $groups; do
+            info "Checking group: '$groupname' against pattern: '$group_pattern'"
+            matchlen=$(expr "$groupname" : "$group_pattern")
+            if [ "$matchlen" == "${#groupname}" ]; then
+              grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
+            fi
+          done
+        done
+        deny "None of the user's groups are in the access list for this branch"
+      fi
+    done
+  )
+  case "$rc" in
+    grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
+    deny)  deny  >/dev/null "Denying  access based on $allowed_groups_file" ;;
+    *) ;;
+  esac
+fi
+
+deny >/dev/null "There are no more rules to check.  Denying access"
+
+-- >8 -- end of script -- >8 --
+
+This uses two files, $GIT_DIR/info/allowed-users and
+allowed-groups, to describe which heads can be pushed into by
+whom.  The format of each file would look like this:
+
+       refs/heads/master       junio
+        refs/heads/cogito$     pasky
+       refs/heads/bw/          linus
+        refs/heads/tmp/                *
+        refs/tags/v[0-9]*      junio
+
+With this, Linus can push or create "bw/penguin" or "bw/zebra"
+or "bw/panda" branches, Pasky can do only "cogito", and JC can
+do master branch and make versioned tags.  And anybody can do
+tmp/blah branches.
+
+------------
diff --git a/Documentation/howto/using-topic-branches.txt b/Documentation/howto/using-topic-branches.txt
new file mode 100644 (file)
index 0000000..2c98194
--- /dev/null
@@ -0,0 +1,296 @@
+Date: Mon, 15 Aug 2005 12:17:41 -0700
+From: tony.luck@intel.com
+Subject: Some tutorial text (was git/cogito workshop/bof at linuxconf au?)
+Abstract: In this article, Tony Luck discusses how he uses GIT
+ as a Linux subsystem maintainer.
+
+Here's something that I've been putting together on how I'm using
+GIT as a Linux subsystem maintainer.
+
+-Tony
+
+Last updated w.r.t. GIT 1.1
+
+Linux subsystem maintenance using GIT
+-------------------------------------
+
+My requirements here are to be able to create two public trees:
+
+1) A "test" tree into which patches are initially placed so that they
+can get some exposure when integrated with other ongoing development.
+This tree is available to Andrew for pulling into -mm whenever he wants.
+
+2) A "release" tree into which tested patches are moved for final
+sanity checking, and as a vehicle to send them upstream to Linus
+(by sending him a "please pull" request.)
+
+Note that the period of time that each patch spends in the "test" tree
+is dependent on the complexity of the change.  Since GIT does not support
+cherry picking, it is not practical to simply apply all patches to the
+test tree and then pull to the release tree as that would leave trivial
+patches blocked in the test tree waiting for complex changes to accumulate
+enough test time to graduate.
+
+Back in the BitKeeper days I achieved this by creating small forests of
+temporary trees, one tree for each logical grouping of patches, and then
+pulling changes from these trees first to the test tree, and then to the
+release tree.  At first I replicated this in GIT, but then I realised
+that I could so this far more efficiently using branches inside a single
+GIT repository.
+
+So here is the step-by-step guide how this all works for me.
+
+First create your work tree by cloning Linus's public tree:
+
+ $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work
+
+Change directory into the cloned tree you just created
+
+ $ cd work
+
+Set up a remotes file so that you can fetch the latest from Linus' master
+branch into a local branch named "linus":
+
+ $ cat > .git/remotes/linus
+ URL: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+ Pull: master:linus
+ ^D
+
+and create the linus branch:
+
+ $ git branch linus
+
+The "linus" branch will be used to track the upstream kernel.  To update it,
+you simply run:
+
+ $ git fetch linus
+
+you can do this frequently (and it should be safe to do so with pending
+work in your tree, but perhaps not if you are in mid-merge).
+
+If you need to keep track of other public trees, you can add remote branches
+for them too:
+
+ $ git branch another
+ $ cat > .git/remotes/another
+ URL: ... insert URL here ...
+ Pull: name-of-branch-in-this-remote-tree:another
+ ^D
+
+and run:
+
+ $ git fetch another
+
+Now create the branches in which you are going to work, these start
+out at the current tip of the linus branch.
+
+ $ git branch test linus
+ $ git branch release linus
+
+These can be easily kept up to date by merging from the "linus" branch:
+
+ $ git checkout test && git merge "Auto-update from upstream" test linus
+ $ git checkout release && git merge "Auto-update from upstream" release linus
+
+Important note!  If you have any local changes in these branches, then
+this merge will create a commit object in the history (with no local
+changes git will simply do a "Fast forward" merge).  Many people dislike
+the "noise" that this creates in the Linux history, so you should avoid
+doing this capriciously in the "release" branch, as these noisy commits
+will become part of the permanent history when you ask Linus to pull
+from the release branch.
+
+Set up so that you can push upstream to your public tree (you need to
+log-in to the remote system and create an empty tree there before the
+first push).
+
+ $ cat > .git/remotes/mytree
+ URL: master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
+ Push: release
+ Push: test
+ ^D
+
+and the push both the test and release trees using:
+
+ $ git push mytree
+
+or push just one of the test and release branches using:
+
+ $ git push mytree test
+or
+ $ git push mytree release
+
+Now to apply some patches from the community.  Think of a short
+snappy name for a branch to hold this patch (or related group of
+patches), and create a new branch from the current tip of the
+linus branch:
+
+ $ git checkout -b speed-up-spinlocks linus
+
+Now you apply the patch(es), run some tests, and commit the change(s).  If
+the patch is a multi-part series, then you should apply each as a separate
+commit to this branch.
+
+ $ ... patch ... test  ... commit [ ... patch ... test ... commit ]*
+
+When you are happy with the state of this change, you can pull it into the
+"test" branch in preparation to make it public:
+
+ $ git checkout test && git merge "Pull speed-up-spinlock changes" test speed-up-spinlocks
+
+It is unlikely that you would have any conflicts here ... but you might if you
+spent a while on this step and had also pulled new versions from upstream.
+
+Some time later when enough time has passed and testing done, you can pull the
+same branch into the "release" tree ready to go upstream.  This is where you
+see the value of keeping each patch (or patch series) in its own branch.  It
+means that the patches can be moved into the "release" tree in any order.
+
+ $ git checkout release && git merge "Pull speed-up-spinlock changes" release speed-up-spinlocks
+
+After a while, you will have a number of branches, and despite the
+well chosen names you picked for each of them, you may forget what
+they are for, or what status they are in.  To get a reminder of what
+changes are in a specific branch, use:
+
+ $ git-whatchanged branchname ^linus | git-shortlog
+
+To see whether it has already been merged into the test or release branches
+use:
+
+ $ git-rev-list branchname ^test
+or
+ $ git-rev-list branchname ^release
+
+[If this branch has not yet been merged you will see a set of SHA1 values
+for the commits, if it has been merged, then there will be no output]
+
+Once a patch completes the great cycle (moving from test to release, then
+pulled by Linus, and finally coming back into your local "linus" branch)
+the branch for this change is no longer needed.  You detect this when the
+output from:
+
+ $ git-rev-list branchname ^linus
+
+is empty.  At this point the branch can be deleted:
+
+ $ git branch -d branchname
+
+Some changes are so trivial that it is not necessary to create a separate
+branch and then merge into each of the test and release branches.  For
+these changes, just apply directly to the "release" branch, and then
+merge that into the "test" branch.
+
+To create diffstat and shortlog summaries of changes to include in a "please
+pull" request to Linus you can use:
+
+ $ git-whatchanged -p release ^linus | diffstat -p1
+and
+ $ git-whatchanged release ^linus | git-shortlog
+
+
+Here are some of the scripts that I use to simplify all this even further.
+
+==== update script ====
+# Update a branch in my GIT tree.  If the branch to be updated
+# is "linus", then pull from kernel.org.  Otherwise merge local
+# linus branch into test|release branch
+
+case "$1" in
+test|release)
+       git checkout $1 && git merge "Auto-update from upstream" $1 linus
+       ;;
+linus)
+       before=$(cat .git/refs/heads/linus)
+       git fetch linus
+       after=$(cat .git/refs/heads/linus)
+       if [ $before != $after ]
+       then
+               git-whatchanged $after ^$before | git-shortlog
+       fi
+       ;;
+*)
+       echo "Usage: $0 linus|test|release" 1>&2
+       exit 1
+       ;;
+esac
+
+==== merge script ====
+# Merge a branch into either the test or release branch
+
+pname=$0
+
+usage()
+{
+       echo "Usage: $pname branch test|release" 1>&2
+       exit 1
+}
+
+if [ ! -f .git/refs/heads/"$1" ]
+then
+       echo "Can't see branch <$1>" 1>&2
+       usage
+fi
+
+case "$2" in
+test|release)
+       if [ $(git-rev-list $1 ^$2 | wc -c) -eq 0 ]
+       then
+               echo $1 already merged into $2 1>&2
+               exit 1
+       fi
+       git checkout $2 && git merge "Pull $1 into $2 branch" $2 $1
+       ;;
+*)
+       usage
+       ;;
+esac
+
+==== status script ====
+# report on status of my ia64 GIT tree
+
+gb=$(tput setab 2)
+rb=$(tput setab 1)
+restore=$(tput setab 9)
+
+if [ `git-rev-list release ^test | wc -c` -gt 0 ]
+then
+       echo $rb Warning: commits in release that are not in test $restore
+       git-whatchanged release ^test
+fi
+
+for branch in `ls .git/refs/heads`
+do
+       if [ $branch = linus -o $branch = test -o $branch = release ]
+       then
+               continue
+       fi
+
+       echo -n $gb ======= $branch ====== $restore " "
+       status=
+       for ref in test release linus
+       do
+               if [ `git-rev-list $branch ^$ref | wc -c` -gt 0 ]
+               then
+                       status=$status${ref:0:1}
+               fi
+       done
+       case $status in
+       trl)
+               echo $rb Need to pull into test $restore
+               ;;
+       rl)
+               echo "In test"
+               ;;
+       l)
+               echo "Waiting for linus"
+               ;;
+       "")
+               echo $rb All done $restore
+               ;;
+       *)
+               echo $rb "<$status>" $restore
+               ;;
+       esac
+       git-whatchanged $branch ^linus | git-shortlog
+done
diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh
new file mode 100755 (executable)
index 0000000..60211a5
--- /dev/null
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+T="$1"
+
+for h in *.html *.txt howto/*.txt howto/*.html
+do
+       if test -f "$T/$h" &&
+          diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
+       then
+               :; # up to date
+       else
+               echo >&2 "# install $h $T/$h"
+               rm -f "$T/$h"
+               mkdir -p `dirname "$T/$h"`
+               cp "$h" "$T/$h"
+       fi
+done
+strip_leading=`echo "$T/" | sed -e 's|.|.|g'`
+for th in "$T"/*.html "$T"/*.txt "$T"/howto/*.txt "$T"/howto/*.html
+do
+       h=`expr "$th" : "$strip_leading"'\(.*\)'`
+       case "$h" in
+       index.html) continue ;;
+       esac
+       test -f "$h" && continue
+       echo >&2 "# rm -f $th"
+       rm -f "$th"
+done
+ln -sf git.html "$T/index.html"
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
new file mode 100644 (file)
index 0000000..53cc355
--- /dev/null
@@ -0,0 +1,16 @@
+-n, \--no-summary::
+       Do not show diffstat at the end of the merge.
+
+--no-commit::
+       Perform the merge but pretend the merge failed and do
+       not autocommit, to give the user a chance to inspect and
+       further tweak the merge result before committing.
+
+
+-s <strategy>, \--strategy=<strategy>::
+       Use the given merge strategy; can be supplied more than
+       once to specify them in the order they should be tried.
+       If there is no `-s` option, a built-in list of strategies
+       is used instead (`git-merge-recursive` when merging a single
+       head, `git-merge-octopus` otherwise).
+
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
new file mode 100644 (file)
index 0000000..7df0266
--- /dev/null
@@ -0,0 +1,35 @@
+MERGE STRATEGIES
+----------------
+
+resolve::
+       This can only resolve two heads (i.e. the current branch
+       and another branch you pulled from) using 3-way merge
+       algorithm.  It tries to carefully detect criss-cross
+       merge ambiguities and is considered generally safe and
+       fast.
+
+recursive::
+       This can only resolve two heads using 3-way merge
+       algorithm.  When there are more than one common
+       ancestors that can be used for 3-way merge, it creates a
+       merged tree of the common ancestors and uses that as
+       the reference tree for the 3-way merge.  This has been
+       reported to result in fewer merge conflicts without
+       causing mis-merges by tests done on actual merge commits
+       taken from Linux 2.6 kernel development history.
+       Additionally this can detect and handle merges involving
+       renames.  This is the default merge strategy when
+       pulling or merging one branch.
+
+octopus::
+       This resolves more than two-head case, but refuses to do
+       complex merge that needs manual resolution.  It is
+       primarily meant to be used for bundling topic branch
+       heads together.  This is the default merge strategy when
+       pulling or merging more than one branches.
+
+ours::
+       This resolves any number of heads, but the result of the
+       merge is always the current branch head.  It is meant to
+       be used to supersede old development history of side
+       branches.
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
new file mode 100644 (file)
index 0000000..e852f41
--- /dev/null
@@ -0,0 +1,69 @@
+<repository>::
+       The "remote" repository that is the source of a fetch
+       or pull operation.  See the section <<URLS,GIT URLS>> below.
+
+<refspec>::
+       The canonical format of a <refspec> parameter is
+       `+?<src>:<dst>`; that is, an optional plus `+`, followed
+       by the source ref, followed by a colon `:`, followed by
+       the destination ref.
++
+The remote ref that matches <src>
+is fetched, and if <dst> is not empty string, the local
+ref that matches it is fast forwarded using <src>.
+Again, if the optional plus `+` is used, the local ref
+is updated even if it does not result in a fast forward
+update.
++
+[NOTE]
+If the remote branch from which you want to pull is
+modified in non-linear ways such as being rewound and
+rebased frequently, then a pull will attempt a merge with
+an older version of itself, likely conflict, and fail.
+It is under these conditions that you would want to use
+the `+` sign to indicate non-fast-forward updates will
+be needed.  There is currently no easy way to determine
+or declare that a branch will be made available in a
+repository with this behavior; the pulling user simply
+must know this is the expected usage pattern for a branch.
++
+[NOTE]
+You never do your own development on branches that appear
+on the right hand side of a <refspec> colon on `Pull:` lines;
+they are to be updated by `git-fetch`.  If you intend to do
+development derived from a remote branch `B`, have a `Pull:`
+line to track it (i.e. `Pull: B:remote-B`), and have a separate
+branch `my-B` to do your development on top of it.  The latter
+is created by `git branch my-B remote-B` (or its equivalent `git
+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>
+directly on `git-pull` command line and having multiple
+`Pull:` <refspec> lines for a <repository> and running
+`git-pull` command without any explicit <refspec> parameters.
+<refspec> listed explicitly on the command line are always
+merged into the current branch after fetching.  In other words,
+if you list more than one remote refs, you would be making
+an Octopus.  While `git-pull` run without any explicit <refspec>
+parameter takes default <refspec>s from `Pull:` lines, it
+merges only the first <refspec> found into the current branch,
+after fetching all the remote refs.  This is because making an
+Octopus from remote refs is rarely done, while keeping track
+of multiple remote heads in one-go by fetching more than one
+is often useful.
++
+Some short-cut notations are also supported.
++
+* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; 
+  it requests fetching everything up to the given tag.
+* A parameter <ref> without a colon is equivalent to
+  <ref>: when pulling/fetching, so it merges <ref> into the current
+  branch without storing the remote branch anywhere locally
diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt
new file mode 100644 (file)
index 0000000..b52dfdc
--- /dev/null
@@ -0,0 +1,141 @@
+git repository layout
+=====================
+
+You may find these things in your git repository (`.git`
+directory for a repository associated with your working tree, or
+`'project'.git` directory for a public 'bare' repository).
+
+objects::
+       Object store associated with this repository.  Usually
+       an object store is self sufficient (i.e. all the objects
+       that are referred to by an object found in it are also
+       found in it), but there are couple of ways to violate
+       it.
++
+. You could populate the repository by running a commit walker
+without `-a` option.  Depending on which options are given, you
+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 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
+of incomplete object store is not suitable to be published for
+use with dumb transports but otherwise is OK as long as
+`objects/info/alternates` points at the right object stores
+it borrows from.
+
+objects/[0-9a-f][0-9a-f]::
+       Traditionally, each object is stored in its own file.
+       They are split into 256 subdirectories using the first
+       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.
+
+objects/pack::
+       Packs (files that store many object in compressed form,
+       along with index files to allow them to be randomly
+       accessed) are found in this directory.
+
+objects/info::
+       Additional information about the object store is
+       recorded in this directory.
+
+objects/info/packs::
+       This file is to help dumb transports discover what packs
+       are available in this object store.  Whenever a pack is
+       added or removed, `git update-server-info` should be run
+       to keep this file up-to-date if the repository is
+       published for dumb transports.  `git repack` does this
+       by default.
+
+objects/info/alternates::
+       This file records absolute filesystem paths of alternate
+       object stores that this object store borrows objects
+       from, one pathname per line.
+
+refs::
+       References are stored in subdirectories of this
+       directory.  The `git prune` command knows to keep
+       objects reachable from refs found in this directory and
+       its subdirectories.
+
+refs/heads/`name`::
+       records tip-of-the-tree commit objects of branch `name`
+
+refs/tags/`name`::
+       records any object name (not necessarily a commit
+       object, or a tag object that points at a commit object).
+
+HEAD::
+       A symlink of the form `refs/heads/'name'` to point at
+       the current branch, if exists.  It does not mean much if
+       the repository is not associated with any working tree
+       (i.e. a 'bare' repository), but a valid git repository
+       *must* have such a symlink here.  It is legal if the
+       named branch 'name' does not (yet) exist.
+
+branches::
+       A slightly deprecated way to store shorthands to be used
+       to specify URL to `git fetch`, `git pull` and `git push`
+       commands is to store a file in `branches/'name'` and
+       give 'name' to these commands in place of 'repository'
+       argument.
+
+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
+       default.  To enable, they need to be made executable.
+       Read link:hooks.html[hooks] for more details about
+       each hook.
+
+index::
+       The current index file for the repository.  It is
+       usually not found in a bare repository.
+
+info::
+       Additional information about the repository is recorded
+       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.
+
+info/grafts::
+       This file records fake commit ancestry information, to
+       pretend the set of parents a commit has is different
+       from how the commit was actually created.  One record
+       per line describes a commit and its fake parents by
+       listing their 40-byte hexadecimal object names separated
+       by a space and terminated by a newline.
+
+info/exclude::
+       This file, by convention among Porcelains, stores the
+       exclude pattern list.  `git status` looks at it, but
+       otherwise it is not looked at by any of the core git
+       commands.
+
+remotes::
+       Stores shorthands to be used to give URL and default
+       refnames to interact with remote repository to `git
+       fetch`, `git pull` and `git push` commands.
+
+logs::
+       Records of changes made to refs are stored in this
+       directory.  See the documentation on git-update-ref
+       for more information.
+
+logs/refs/heads/`name`::
+       Records all changes made to the branch tip named `name`.
+
+logs/refs/tags/`name`::
+       Records all changes made to the tag named `name`.
diff --git a/Documentation/sort_glossary.pl b/Documentation/sort_glossary.pl
new file mode 100644 (file)
index 0000000..e0bc552
--- /dev/null
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+%terms=();
+
+while(<>) {
+       if(/^(\S.*)::$/) {
+               my $term=$1;
+               if(defined($terms{$term})) {
+                       die "$1 defined twice\n";
+               }
+               $terms{$term}="";
+               LOOP: while(<>) {
+                       if(/^$/) {
+                               last LOOP;
+                       }
+                       if(/^   \S/) {
+                               $terms{$term}.=$_;
+                       } else {
+                               die "Error 1: $_";
+                       }
+               }
+       }
+}
+
+sub format_tab_80 ($) {
+       my $text=$_[0];
+       my $result="";
+       $text=~s/\s+/ /g;
+       $text=~s/^\s+//;
+       while($text=~/^(.{1,72})(|\s+(\S.*)?)$/) {
+               $result.="      ".$1."\n";
+               $text=$3;
+       }
+       return $result;
+}
+
+sub no_spaces ($) {
+       my $result=$_[0];
+       $result=~tr/ /_/;
+       return $result;
+}
+
+print 'GIT Glossary
+============
+
+This list is sorted alphabetically:
+
+';
+
+@keys=sort {uc($a) cmp uc($b)} keys %terms;
+$pattern='(\b(?<!link:git-)'.join('\b|\b(?<!link:git-)',reverse @keys).'\b)';
+foreach $key (@keys) {
+       $terms{$key}=~s/$pattern/sprintf "<<ref_".no_spaces($1).",$1>>";/eg;
+       print '[[ref_'.no_spaces($key).']]'.$key."::\n"
+               .format_tab_80($terms{$key})."\n";
+}
+
+print '
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de> and
+the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the link:git.html[git] suite
+';
+
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
new file mode 100644 (file)
index 0000000..0e1ffb2
--- /dev/null
@@ -0,0 +1,116 @@
+GIT pack format
+===============
+
+= pack-*.pack file has the following format:
+
+   - The header appears at the beginning and consists of the following:
+
+     4-byte signature:
+         The signature is: {'P', 'A', 'C', 'K'}
+
+     4-byte version number (network byte order):
+         GIT currently accepts version number 2 or 3 but
+         generates version 2 only.
+
+     4-byte number of objects contained in the pack (network byte order)
+
+     Observation: we cannot have more than 4G versions ;-) and
+     more than 4G objects in a pack.
+
+   - The header is followed by number of object entries, each of
+     which looks like this:
+
+     (undeltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     compressed data
+
+     (deltified representation)
+     n-byte type and length (4-bit type, (n-1)*7+4-bit length)
+     20-byte base object name
+     compressed delta data
+
+     Observation: length of each object is encoded in a variable
+     length format and is not constrained to 32-bit or anything.
+
+  - The trailer records 20-byte SHA1 checksum of all of the above.
+
+= pack-*.idx file has the following format:
+
+  - The header consists of 256 4-byte network byte order
+    integers.  N-th entry of this table records the number of
+    objects in the corresponding pack, the first byte of whose
+    object name are smaller than N.  This is called the
+    'first-level fan-out' table.
+
+    Observation: we would need to extend this to an array of
+    8-byte integers to go beyond 4G objects per pack, but it is
+    not strictly necessary.
+
+  - The header is followed by sorted 24-byte entries, one entry
+    per object in the pack.  Each entry is:
+
+    4-byte network byte order integer, recording where the
+    object is stored in the packfile as the offset from the
+    beginning.
+
+    20-byte object name.
+
+    Observation: we would definitely need to extend this to
+    8-byte integer plus 20-byte object name to handle a packfile
+    that is larger than 4GB.
+
+  - The file is concluded with a trailer:
+
+    A copy of the 20-byte SHA1 checksum at the end of
+    corresponding packfile.
+
+    20-byte SHA1-checksum of all of the above.
+
+Pack Idx file:
+
+       idx
+           +--------------------------------+
+           | fanout[0] = 2                  |-.
+           +--------------------------------+ |
+           | fanout[1]                      | |
+           +--------------------------------+ |
+           | fanout[2]                      | |
+           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
+           | fanout[255]                    | |
+           +--------------------------------+ |
+main       | offset                         | |
+index      | object name 00XXXXXXXXXXXXXXXX | |
+table      +--------------------------------+ | 
+           | offset                         | |
+           | object name 00XXXXXXXXXXXXXXXX | |
+           +--------------------------------+ |
+         .-| offset                         |<+
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+         | | offset                         |
+         | | object name 01XXXXXXXXXXXXXXXX |
+         | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+         | | offset                         |
+         | | object name FFXXXXXXXXXXXXXXXX |
+         | +--------------------------------+
+trailer          | | packfile checksum              |
+         | +--------------------------------+
+         | | idxfile checksum               |
+         | +--------------------------------+
+          .-------.      
+                  |
+Pack file entry: <+
+
+     packed object header:
+       1-byte type (upper 4-bit)
+              size0 (lower 4-bit) 
+        n-byte sizeN (as long as MSB is set, each 7-bit)
+               size0..sizeN form 4+7+7+..+7 bit integer, size0
+               is the most significant part.
+     packed object data:
+        If it is not DELTA, then deflated bytes (the size above
+               is the size before compression).
+       If it is DELTA, then
+         20-byte base object name SHA1 (the size above is the
+               size of the delta data that follows).
+          delta data, deflated.
diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.txt
new file mode 100644 (file)
index 0000000..9aadd5c
--- /dev/null
@@ -0,0 +1,466 @@
+        Concerning Git's Packing Heuristics
+        ===================================
+
+        Oh, here's a really stupid question:
+
+                  Where do I go
+               to learn the details
+           of git's packing heuristics?
+
+Be careful what you ask!
+
+Followers of the git, please open the git IRC Log and turn to
+February 10, 2006.
+
+It's a rare occasion, and we are joined by the King Git Himself,
+Linus Torvalds (linus).  Nathaniel Smith, (njs`), has the floor
+and seeks enlightenment.  Others are present, but silent.
+
+Let's listen in!
+
+    <njs`> Oh, here's a really stupid question -- where do I go to
+        learn the details of git's packing heuristics?  google avails
+        me not, reading the source didn't help a lot, and wading
+        through the whole mailing list seems less efficient than any
+        of that.
+
+It is a bold start!  A plea for help combined with a simultaneous
+tri-part attack on some of the tried and true mainstays in the quest
+for enlightenment.  Brash accusations of google being useless. Hubris!
+Maligning the source.  Heresy!  Disdain for the mailing list archives.
+Woe.
+
+    <pasky> yes, the packing-related delta stuff is somewhat
+        mysterious even for me ;)
+
+Ah!  Modesty after all.
+
+    <linus> njs, I don't think the docs exist. That's something where
+        I don't think anybody else than me even really got involved.
+        Most of the rest of git others have been busy with (especially
+        Junio), but packing nobody touched after I did it.
+
+It's cryptic, yet vague.  Linus in style for sure.  Wise men
+interpret this as an apology.  A few argue it is merely a
+statement of fact.
+
+    <njs`> I guess the next step is "read the source again", but I
+        have to build up a certain level of gumption first :-)
+
+Indeed!  On both points.
+
+    <linus> The packing heuristic is actually really really simple.
+
+Bait...
+
+    <linus> But strange.
+
+And switch.  That ought to do it!
+
+    <linus> Remember: git really doesn't follow files. So what it does is
+        - generate a list of all objects
+        - sort the list according to magic heuristics
+        - walk the list, using a sliding window, seeing if an object
+          can be diffed against another object in the window
+        - write out the list in recency order
+
+The traditional understatement:
+
+    <njs`> I suspect that what I'm missing is the precise definition of
+        the word "magic"
+
+The traditional insight:
+
+    <pasky> yes
+
+And Bable-like confusion flowed.
+
+    <njs`> oh, hmm, and I'm not sure what this sliding window means either
+
+    <pasky> iirc, it appeared to me to be just the sha1 of the object
+        when reading the code casually ...
+
+        ... which simply doesn't sound as a very good heuristics, though ;)
+
+    <njs`> .....and recency order.  okay, I think it's clear I didn't
+       even realize how much I wasn't realizing :-)
+
+Ah, grasshopper!  And thus the enlightenment begins anew.
+
+    <linus> The "magic" is actually in theory totally arbitrary.
+        ANY order will give you a working pack, but no, it's not
+        ordered by SHA1.
+
+        Before talking about the ordering for the sliding delta
+        window, let's talk about the recency order. That's more
+        important in one way.
+
+    <njs`> Right, but if all you want is a working way to pack things
+        together, you could just use cat and save yourself some
+        trouble...
+
+Waaait for it....
+
+    <linus> The recency ordering (which is basically: put objects
+        _physically_ into the pack in the order that they are
+        "reachable" from the head) is important.
+
+    <njs`> okay
+
+    <linus> It's important because that's the thing that gives packs
+        good locality. It keeps the objects close to the head (whether
+        they are old or new, but they are _reachable_ from the head)
+        at the head of the pack. So packs actually have absolutely
+        _wonderful_ IO patterns.
+
+Read that again, because it is important.
+
+    <linus> But recency ordering is totally useless for deciding how
+        to actually generate the deltas, so the delta ordering is
+        something else.
+
+        The delta ordering is (wait for it):
+        - first sort by the "basename" of the object, as defined by
+          the name the object was _first_ reached through when
+          generating the object list
+        - within the same basename, sort by size of the object
+        - but always sort different types separately (commits first).
+
+        That's not exactly it, but it's very close.
+
+    <njs`> The "_first_ reached" thing is not too important, just you
+        need some way to break ties since the same objects may be
+        reachable many ways, yes?
+
+And as if to clarify:
+
+    <linus> The point is that it's all really just any random
+        heuristic, and the ordering is totally unimportant for
+        correctness, but it helps a lot if the heuristic gives
+        "clumping" for things that are likely to delta well against
+        each other.
+
+It is an important point, so secretly, I did my own research and have
+included my results below.  To be fair, it has changed some over time.
+And through the magic of Revisionistic History, I draw upon this entry
+from The Git IRC Logs on my father's birthday, March 1:
+
+    <gitster> The quote from the above linus should be rewritten a
+        bit (wait for it):
+        - first sort by type.  Different objects never delta with
+         each other.
+        - then sort by filename/dirname.  hash of the basename
+          occupies the top BITS_PER_INT-DIR_BITS bits, and bottom
+          DIR_BITS are for the hash of leading path elements.
+        - then if we are doing "thin" pack, the objects we are _not_
+          going to pack but we know about are sorted earlier than
+          other objects.
+        - and finally sort by size, larger to smaller.
+
+In one swell-foop, clarification and obscurification!  Nonetheless,
+authoritative.  Cryptic, yet concise.  It even solicits notions of
+quotes from The Source Code.  Clearly, more study is needed.
+
+    <gitster> That's the sort order.  What this means is:
+        - we do not delta different object types.
+       - we prefer to delta the objects with the same full path, but
+          allow files with the same name from different directories.
+       - we always prefer to delta against objects we are not going
+          to send, if there are some.
+       - we prefer to delta against larger objects, so that we have
+          lots of removals.
+
+        The penultimate rule is for "thin" packs.  It is used when
+        the other side is known to have such objects.
+
+There it is again. "Thin" packs.  I'm thinking to myself, "What
+is a 'thin' pack?"  So I ask:
+
+    <jdl> What is a "thin" pack?
+
+    <gitster> Use of --objects-edge to rev-list as the upstream of
+        pack-objects.  The pack transfer protocol negotiates that.
+
+Woo hoo!  Cleared that _right_ up!
+
+    <gitster> There are two directions - push and fetch.
+
+There!  Did you see it?  It is not '"push" and "pull"'!  How often the
+confusion has started here.  So casually mentioned, too!
+
+    <gitster> For push, git-send-pack invokes git-receive-pack on the
+        other end.  The receive-pack says "I have up to these commits".
+        send-pack looks at them, and computes what are missing from
+        the other end.  So "thin" could be the default there.
+
+        In the other direction, fetch, git-fetch-pack and
+        git-clone-pack invokes git-upload-pack on the other end
+       (via ssh or by talking to the daemon).
+
+       There are two cases: fetch-pack with -k and clone-pack is one,
+        fetch-pack without -k is the other.  clone-pack and fetch-pack
+        with -k will keep the downloaded packfile without expanded, so
+        we do not use thin pack transfer.  Otherwise, the generated
+        pack will have delta without base object in the same pack.
+
+        But fetch-pack without -k will explode the received pack into
+        individual objects, so we automatically ask upload-pack to
+        give us a thin pack if upload-pack supports it.
+
+OK then.
+
+Uh.
+
+Let's return to the previous conversation still in progress.
+
+    <njs`> and "basename" means something like "the tail of end of
+        path of file objects and dir objects, as per basename(3), and
+        we just declare all commit and tag objects to have the same
+        basename" or something?
+
+Luckily, that too is a point that gitster clarified for us!
+
+If I might add, the trick is to make files that _might_ be similar be
+located close to each other in the hash buckets based on their file
+names.  It used to be that "foo/Makefile", "bar/baz/quux/Makefile" and
+"Makefile" all landed in the same bucket due to their common basename,
+"Makefile". However, now they land in "close" buckets.
+
+The algorithm allows not just for the _same_ bucket, but for _close_
+buckets to be considered delta candidates.  The rationale is
+essentially that files, like Makefiles, often have very similar
+content no matter what directory they live in.
+
+    <linus> I played around with different delta algorithms, and with
+        making the "delta window" bigger, but having too big of a
+        sliding window makes it very expensive to generate the pack:
+        you need to compare every object with a _ton_ of other objects.
+
+        There are a number of other trivial heuristics too, which
+        basically boil down to "don't bother even trying to delta this
+        pair" if we can tell before-hand that the delta isn't worth it
+        (due to size differences, where we can take a previous delta
+        result into account to decide that "ok, no point in trying
+        that one, it will be worse").
+
+        End result: packing is actually very size efficient. It's
+        somewhat CPU-wasteful, but on the other hand, since you're
+        really only supposed to do it maybe once a month (and you can
+        do it during the night), nobody really seems to care.
+
+Nice Engineering Touch, there.  Find when it doesn't matter, and
+proclaim it a non-issue.  Good style too!
+
+    <njs`> So, just to repeat to see if I'm following, we start by
+        getting a list of the objects we want to pack, we sort it by
+        this heuristic (basically lexicographically on the tuple
+        (type, basename, size)).
+
+        Then we walk through this list, and calculate a delta of
+        each object against the last n (tunable paramater) objects,
+        and pick the smallest of these deltas.
+
+Vastly simplified, but the essence is there!
+
+    <linus> Correct.
+
+    <njs`> And then once we have picked a delta or fulltext to
+        represent each object, we re-sort by recency, and write them
+        out in that order.
+
+    <linus> Yup. Some other small details:
+
+And of course there is the "Other Shoe" Factor too.
+
+    <linus> - We limit the delta depth to another magic value (right
+        now both the window and delta depth magic values are just "10")
+
+    <njs`> Hrm, my intuition is that you'd end up with really _bad_ IO
+        patterns, because the things you want are near by, but to
+        actually reconstruct them you may have to jump all over in
+        random ways.
+
+    <linus> - When we write out a delta, and we haven't yet written
+        out the object it is a delta against, we write out the base
+        object first.  And no, when we reconstruct them, we actually
+        get nice IO patterns, because:
+        - larger objects tend to be "more recent" (Linus' law: files grow)
+        - we actively try to generate deltas from a larger object to a
+          smaller one
+        - this means that the top-of-tree very seldom has deltas
+          (i.e. deltas in _practice_ are "backwards deltas")
+
+Again, we should reread that whole paragraph.  Not just because
+Linus has slipped Linus's Law in there on us, but because it is
+important.  Let's make sure we clarify some of the points here:
+
+    <njs`> So the point is just that in practice, delta order and
+        recency order match each other quite well.
+
+    <linus> Yes. There's another nice side to this (and yes, it was
+       designed that way ;):
+        - the reason we generate deltas against the larger object is
+         actually a big space saver too!
+
+    <njs`> Hmm, but your last comment (if "we haven't yet written out
+        the object it is a delta against, we write out the base object
+        first"), seems like it would make these facts mostly
+        irrelevant because even if in practice you would not have to
+        wander around much, in fact you just brute-force say that in
+        the cases where you might have to wander, don't do that :-)
+
+    <linus> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.  That
+        means that you can actually have deltas that refer to a base
+        object that is _not_ close to the delta object, but that only
+        happens when the delta is needed to generate an _old_ object.
+
+    <linus> See?
+
+Yeah, no.  I missed that on the first two or three readings myself.
+
+    <linus> This keeps the front of the pack dense. The front of the
+        pack never contains data that isn't relevant to a "recent"
+        object.  The size optimization comes from our use of xdelta
+        (but is true for many other delta algorithms): removing data
+        is cheaper (in size) than adding data.
+
+        When you remove data, you only need to say "copy bytes n--m".
+       In contrast, in a delta that _adds_ data, you have to say "add
+        these bytes: 'actual data goes here'"
+
+    *** njs` has quit: Read error: 104 (Connection reset by peer)
+
+    <linus> Uhhuh. I hope I didn't blow njs` mind.
+
+    *** njs` has joined channel #git
+
+    <pasky> :)
+
+The silent observers are amused.  Of course.
+
+And as if njs` was expected to be omniscient:
+
+    <linus> njs - did you miss anything?
+
+OK, I'll spell it out.  That's Geek Humor.  If njs` was not actually
+connected for a little bit there, how would he know if missed anything
+while he was disconnected?  He's a benevolent dictator with a sense of
+humor!  Well noted!
+
+    <njs`> Stupid router.  Or gremlins, or whatever.
+
+It's a cheap shot at Cisco.  Take 'em when you can.
+
+    <njs`> Yes and no. Notice the rule: we only write out the base
+        object first if the delta against it was more recent.
+
+        I'm getting lost in all these orders, let me re-read :-)
+       So the write-out order is from most recent to least recent?
+        (Conceivably it could be the opposite way too, I'm not sure if
+        we've said) though my connection back at home is logging, so I
+        can just read what you said there :-)
+
+And for those of you paying attention, the Omniscient Trick has just
+been detailed!
+
+    <linus> Yes, we always write out most recent first
+
+For the other record:
+
+    <pasky> njs`: http://pastebin.com/547965
+
+The 'net never forgets, so that should be good until the end of time.
+
+    <njs`> And, yeah, I got the part about deeper-in-history stuff
+        having worse IO characteristics, one sort of doesn't care.
+
+    <linus> With the caveat that if the "most recent" needs an older
+        object to delta against (hey, shrinking sometimes does
+        happen), we write out the old object with the delta.
+
+    <njs`> (if only it happened more...)
+
+    <linus> Anyway, the pack-file could easily be denser still, but
+        because it's used both for streaming (the git protocol) and
+        for on-disk, it has a few pessimizations.
+
+Actually, it is a made-up word. But it is a made-up word being
+used as setup for a later optimization, which is a real word:
+
+    <linus> In particular, while the pack-file is then compressed,
+        it's compressed just one object at a time, so the actual
+        compression factor is less than it could be in theory. But it
+        means that it's all nice random-access with a simple index to
+        do "object name->location in packfile" translation.
+
+    <njs`> I'm assuming the real win for delta-ing large->small is
+        more homogenous statistics for gzip to run over?
+
+        (You have to put the bytes in one place or another, but
+        putting them in a larger blob wins on compression)
+
+        Actually, what is the compression strategy -- each delta
+        individually gzipped, the whole file gzipped, somewhere in
+        between, no compression at all, ....?
+
+        Right.
+
+Reality IRC sets in.  For example:
+
+    <pasky> I'll read the rest in the morning, I really have to go
+        sleep or there's no hope whatsoever for me at the today's
+        exam... g'nite all.
+
+Heh.
+
+    <linus> pasky: g'nite
+
+    <njs`> pasky: 'luck
+
+    <linus> Right: large->small matters exactly because of compression
+        behaviour. If it was non-compressed, it probably wouldn't make
+        any difference.
+
+    <njs`> yeah
+
+    <linus> Anyway: I'm not even trying to claim that the pack-files
+        are perfect, but they do tend to have a nice balance of
+        density vs ease-of use.
+
+Gasp!  OK, saved.  That's a fair Engineering trade off.  Close call!
+In fact, Linus reflects on some Basic Engineering Fundamentals,
+design options, etc.
+
+    <linus> More importantly, they allow git to still _conceptually_
+        never deal with deltas at all, and be a "whole object" store.
+
+        Which has some problems (we discussed bad huge-file
+        behaviour on the git lists the other day), but it does mean
+        that the basic git concepts are really really simple and
+        straightforward.
+
+        It's all been quite stable.
+
+        Which I think is very much a result of having very simple
+        basic ideas, so that there's never any confusion about what's
+        going on.
+
+        Bugs happen, but they are "simple" bugs. And bugs that
+        actually get some object store detail wrong are almost always
+        so obious that they never go anywhere.
+
+    <njs`> Yeah.
+
+Nuff said.
+
+    <linus> Anyway.  I'm off for bed. It's not 6AM here, but I've got
+        three kids, and have to get up early in the morning to send
+        them off. I need my beauty sleep.
+
+    <njs`> :-)
+
+    <njs`> appreciate the infodump, I really was failing to find the
+        details on git packs :-)
+
+And now you know the rest of the story.
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
new file mode 100644 (file)
index 0000000..9cd48b4
--- /dev/null
@@ -0,0 +1,41 @@
+Pack transfer protocols
+=======================
+
+There are two Pack push-pull protocols.
+
+upload-pack (S) | fetch/clone-pack (C) protocol:
+
+       # Tell the puller what commits we have and what their names are
+       S: SHA1 name
+       S: ...
+       S: SHA1 name
+       S: # flush -- it's your turn
+       # Tell the pusher what commits we want, and what we have
+       C: want name
+       C: ..
+       C: want name
+       C: have SHA1
+       C: have SHA1
+       C: ...
+       C: # flush -- occasionally ask "had enough?"
+       S: NAK
+       C: have SHA1
+       C: ...
+       C: have SHA1
+       S: ACK
+       C: done
+       S: XXXXXXX -- packfile contents.
+
+send-pack | receive-pack protocol.
+
+       # Tell the pusher what commits we have and what their names are
+       C: SHA1 name
+       C: ...
+       C: SHA1 name
+       C: # flush -- it's your turn
+       # Tell the puller what the pusher has
+       S: old-SHA1 new-SHA1 name
+       S: old-SHA1 new-SHA1 name
+       S: ...
+       S: # flush -- done with the list
+       S: XXXXXXX --- packfile contents.
diff --git a/Documentation/technical/trivial-merge.txt b/Documentation/technical/trivial-merge.txt
new file mode 100644 (file)
index 0000000..24c8410
--- /dev/null
@@ -0,0 +1,121 @@
+Trivial merge rules
+===================
+
+This document describes the outcomes of the trivial merge logic in read-tree.
+
+One-way merge
+-------------
+
+This replaces the index with a different tree, keeping the stat info
+for entries that don't change, and allowing -u to make the minimum
+required changes to the working tree to have it match.
+
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
+
+   index   tree    result
+   -----------------------
+   *       (empty) (empty)
+   (empty) tree    tree
+   index+  tree    tree
+   index+  index   index+
+
+Two-way merge
+-------------
+
+It is permitted for the index to lack an entry; this does not prevent
+any case from applying.
+
+If the index exists, it is an error for it not to match either the old
+or the result.
+
+If multiple cases apply, the one used is listed first.
+
+A result which changes the index is an error if the index is not empty
+and not up-to-date.
+
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
+
+ case  index   old     new     result
+ -------------------------------------
+ 0/2   (empty) *       (empty) (empty)
+ 1/3   (empty) *       new     new
+ 4/5   index+  (empty) (empty) index+
+ 6/7   index+  (empty) index   index+
+ 10    index+  index   (empty) (empty)
+ 14/15 index+  old     old     index+
+ 18/19 index+  old     index   index+
+ 20    index+  index   new     new
+
+Three-way merge
+---------------
+
+It is permitted for the index to lack an entry; this does not prevent
+any case from applying.
+
+If the index exists, it is an error for it not to match either the
+head or (if the merge is trivial) the result.
+
+If multiple cases apply, the one used is listed first.
+
+A result of "no merge" means that index is left in stage 0, ancest in
+stage 1, head in stage 2, and remote in stage 3 (if any of these are
+empty, no entry is left for that stage). Otherwise, the given entry is
+left in stage 0, and there are no other entries.
+
+A result of "no merge" is an error if the index is not empty and not
+up-to-date.
+
+*empty* means that the tree must not have a directory-file conflict
+ with the entry.
+
+For multiple ancestors, a '+' means that this case applies even if
+only one ancestor or remote fits; a '^' means all of the ancestors
+must be the same.
+
+case  ancest    head    remote    result
+----------------------------------------
+1     (empty)+  (empty) (empty)   (empty)
+2ALT  (empty)+  *empty* remote    remote
+2     (empty)^  (empty) remote    no merge
+3ALT  (empty)+  head    *empty*   head
+3     (empty)^  head    (empty)   no merge
+4     (empty)^  head    remote    no merge
+5ALT  *         head    head      head
+6     ancest+   (empty) (empty)   no merge
+8     ancest^   (empty) ancest    no merge
+7     ancest+   (empty) remote    no merge
+10    ancest^   ancest  (empty)   no merge
+9     ancest+   head    (empty)   no merge
+16    anc1/anc2 anc1    anc2      no merge
+13    ancest+   head    ancest    head
+14    ancest+   ancest  remote    remote
+11    ancest+   head    remote    no merge
+
+Only #2ALT and #3ALT use *empty*, because these are the only cases
+where there can be conflicts that didn't exist before. Note that we
+allow directory-file conflicts between things in different stages
+after the trivial merge.
+
+A possible alternative for #6 is (empty), which would make it like
+#1. This is not used, due to the likelihood that it arises due to
+moving the file to multiple different locations or moving and deleting
+it in different branches.
+
+Case #1 is included for completeness, and also in case we decide to
+put on '+' markings; any path that is never mentioned at all isn't
+handled.
+
+Note that #16 is when both #13 and #14 apply; in this case, we refuse
+the trivial merge, because we can't tell from this data which is
+right. This is a case of a reverted patch (in some direction, maybe
+multiple times), and the right answer depends on looking at crossings
+of history or common ancestors of the ancestors.
+
+Note that, between #6, #7, #9, and #11, all cases not otherwise
+covered are handled in this table.
+
+For #8 and #10, there is alternative behavior, not currently
+implemented, where the result is (empty). As currently implemented,
+the automatic merge will generally give this effect.
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
new file mode 100644 (file)
index 0000000..894ca5e
--- /dev/null
@@ -0,0 +1,391 @@
+A tutorial introduction to git: part two
+========================================
+
+You should work through link:tutorial.html[A tutorial introduction to
+git] before reading this tutorial.
+
+The goal of this tutorial is to introduce two fundamental pieces of
+git's architecture--the object database and the index file--and to
+provide the reader with everything necessary to understand the rest
+of the git documentation.
+
+The git object database
+-----------------------
+
+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
+$ echo 'hello world' > file.txt
+$ git add .
+$ git commit -a -m "initial commit"
+Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe
+$ echo 'hello world!' >file.txt
+$ git commit -a -m "add emphasis"
+------------------------------------------------
+
+What are the 40 digits of hex that git responded to the first 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
+such a 40-digit hex name.  That name is the SHA1 hash of the object's
+contents; among other things, this ensures that git will never store
+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).
+
+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:
+
+------------------------------------------------
+$ git cat-file -t 92b8b694ffb1675e5975148e1121810081dbdffe
+tree
+------------------------------------------------
+
+A tree can refer to one or more "blob" objects, each corresponding to
+a file.  In addition, a tree can also refer to other tree objects,
+thus creating a directory hierarchy.  You can examine the contents of
+any tree using ls-tree (remember that a long enough initial portion
+of the SHA1 will also work):
+
+------------------------------------------------
+$ git ls-tree 92b8b694
+100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt
+------------------------------------------------
+
+Thus we see that this tree has one file in it.  The SHA1 hash is a
+reference to that file's data:
+
+------------------------------------------------
+$ git cat-file -t 3b18e512
+blob
+------------------------------------------------
+
+A "blob" is just file data, which we can also examine with cat-file:
+
+------------------------------------------------
+$ git cat-file blob 3b18e512
+hello world
+------------------------------------------------
+
+Note that this is the old file data; so the object that git named in
+its response to the initial tree was a tree with a snapshot of the
+directory state that was recorded by the first commit.
+
+All of these objects are stored under their SHA1 names inside the git
+directory:
+
+------------------------------------------------
+$ find .git/objects/
+.git/objects/
+.git/objects/pack
+.git/objects/info
+.git/objects/3b
+.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
+.git/objects/92
+.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
+.git/objects/54
+.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
+.git/objects/a0
+.git/objects/a0/423896973644771497bdc03eb99d5281615b51
+.git/objects/d0
+.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
+.git/objects/c4
+.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+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.
+
+The simplest commit to find is the HEAD commit, which we can find
+from .git/HEAD:
+
+------------------------------------------------
+$ cat .git/HEAD
+ref: refs/heads/master
+------------------------------------------------
+
+As you can see, this tells us which branch we're currently on, and it
+tells us this by naming a file under the .git directory, which itself
+contains a SHA1 name referring to a commit object, which we can
+examine with cat-file:
+
+------------------------------------------------
+$ cat .git/refs/heads/master
+c4d59f390b9cfd4318117afde11d601c1085f241
+$ git cat-file -t c4d59f39
+commit
+$ git cat-file commit c4d59f39
+tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
+parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+
+add emphasis
+------------------------------------------------
+
+The "tree" object here refers to the new state of the tree:
+
+------------------------------------------------
+$ git ls-tree d0492b36
+100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
+$ git cat-file blob a0423896
+hello world!
+------------------------------------------------
+
+and the "parent" object refers to the previous 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
+------------------------------------------------
+
+The tree object is the tree we examined first, and this commit is
+unusual in that it lacks any parent.
+
+Most commits have only one parent, but it is also common for a commit
+to have multiple parents.   In that case the commit represents a
+merge, with the parent references pointing to the heads of the merged
+branches.
+
+Besides blobs, trees, and commits, the only remaining type of object
+is a "tag", which we won't discuss here; refer to gitlink:git-tag[1]
+for details.
+
+So now we know how git uses the object database to represent a
+project's history:
+
+  * "commit" objects refer to "tree" objects representing the
+    snapshot of a directory tree at a particular point in the
+    history, and refer to "parent" commits to show how they're
+    connected into the project history.
+  * "tree" objects represent the state of a single directory,
+    associating directory names to "blob" objects containing file
+    data and "tree" objects containing subdirectory information.
+  * "blob" objects contain file data without any other structure.
+  * References to commit objects at the head of each branch are
+    stored in files under .git/refs/heads/.
+  * The name of the current branch is stored in .git/HEAD.
+
+Note, by the way, that lots of commands take a tree as an argument.
+But as we can see above, a tree can be referred to in many different
+ways--by the SHA1 name for that tree, by the name of a commit that
+refers to the tree, by the name of a branch whose head refers to that
+tree, etc.--and most such commands can accept any of these names.
+
+In command synopses, the word "tree-ish" is sometimes used to
+designate such an argument.
+
+The index file
+--------------
+
+The primary tool we've been using to create commits is "git commit
+-a", which creates a commit including every change you've made to
+your working tree.  But what if you want to commit changes only to
+certain files?  Or only certain changes to certain files?
+
+If we look at the way commits are created under the cover, we'll see
+that there are more flexible ways creating commits.
+
+Continuing with our test-project, let's modify file.txt again:
+
+------------------------------------------------
+$ echo "hello world, again" >>file.txt
+------------------------------------------------
+
+but this time instead of immediately making the commit, let's take an
+intermediate step, and ask for diffs along the way to keep track of
+what's happening:
+
+------------------------------------------------
+$ git diff
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+$ git update-index file.txt
+$ git diff
+------------------------------------------------
+
+The last diff is empty, but no new commits have been made, and the
+head still doesn't contain the new line:
+
+------------------------------------------------
+$ git-diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+So "git diff" is comparing against something other than the head.
+The thing that it's comparing against is actually the index file,
+which is stored in .git/index in a binary format, but whose contents
+we can examine with ls-files:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+$ git cat-file -t 513feba2
+blob
+$ git cat-file blob 513feba2
+hello world, again
+------------------------------------------------
+
+So what our "git update-index" did was store a new blob and then put
+a reference to it in the index file.  If we modify the file again,
+we'll see that the new modifications are reflected in the "git-diff"
+output:
+
+------------------------------------------------
+$ echo 'again?' >>file.txt
+$ git diff
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+With the right arguments, git diff can also show us the difference
+between the working directory and the last commit, or between the
+index and the last commit:
+
+------------------------------------------------
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,3 @@
+ hello world!
++hello world, again
++again?
+$ git diff --cached
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+At any time, we can create a new commit using "git commit" (without
+the -a option), and verify that the state committed only includes the
+changes stored in the index file, not the additional change that is
+still only in our working tree:
+
+------------------------------------------------
+$ git commit -m "repeat"
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+So by default "git commit" uses the index to create the commit, not
+the working tree; the -a option to commit tells it to first update
+the index with all changes in the working tree.
+
+Finally, it's worth looking at the effect of "git add" on the index
+file:
+
+------------------------------------------------
+$ echo "goodbye, world" >closing.txt
+$ git add closing.txt
+------------------------------------------------
+
+The effect of the "git add" was to add one entry to the index file:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+------------------------------------------------
+
+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
+------------------------------------------------
+
+The "status" command is a useful way to get a quick summary of the
+situation:
+
+------------------------------------------------
+$ git status
+#
+# Updated but not checked in:
+#   (will commit)
+#
+#       new file: closing.txt
+#
+#
+# Changed but not updated:
+#   (use git-update-index to mark for commit)
+#
+#       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
+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
+contents), but that didn't modify file.txt.
+
+Also, note that a bare "git diff" shows the changes to file.txt, but
+not the addition of closing.txt, because the version of closing.txt
+in the index file is identical to the one in the working directory.
+
+In addition to being the staging area for new commits, the index file
+is also populated from the object database when checking out a
+branch, and is used to hold the trees involved in a merge operation.
+See the link:core-tutorial.txt[core tutorial] and the relevant man
+pages for details.
+
+What next?
+----------
+
+At this point you should know everything necessary to read the man
+pages for any of the git commands; one good place to start would be
+with the commands mentioned in link:everyday.html[Everyday git].  You
+should be able to find any unknown jargon in the
+link:glossary.html[Glossary].
+
+The link:cvs-migration.html[CVS migration] document explains how to
+import a CVS repository into git, and shows how to use git in a
+CVS-like way.
+
+For some interesting examples of git use, see the
+link:howto-index.html[howtos].
+
+For git developers, the link:core-tutorial.html[Core tutorial] goes
+into detail on the lower-level git mechanisms involved in, for
+example, creating a new commit.
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
new file mode 100644 (file)
index 0000000..db56312
--- /dev/null
@@ -0,0 +1,487 @@
+A tutorial introduction to git
+==============================
+
+This tutorial explains how to import a new project into git, make
+changes to it, and share changes with other developers.
+
+First, note that you can get documentation for a command such as "git
+diff" with:
+
+------------------------------------------------
+$ man git-diff
+------------------------------------------------
+
+Importing a new project
+-----------------------
+
+Assume you have a tarball project.tar.gz with your initial work.  You
+can place it under git revision control as follows.
+
+------------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init-db
+------------------------------------------------
+
+Git will reply
+
+------------------------------------------------
+defaulting to local storage area
+------------------------------------------------
+
+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
+
+------------------------------------------------
+$ git add .
+------------------------------------------------
+
+Finally,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will prompt you for a commit message, then record the current state
+of all the files to the repository.
+
+Try modifying some files, then run
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+to review your changes.  When you're done,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will again prompt your for a message describing the change, and then
+record the new versions of the modified 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
+commit in the body.
+
+To add a new file, first create the file, then
+
+------------------------------------------------
+$ git add path/to/new/file
+------------------------------------------------
+
+then commit as usual.  No special command is required when removing a
+file; just remove it, then commit.
+
+At any point you can view the history of your changes using
+
+------------------------------------------------
+$ git log
+------------------------------------------------
+
+If you also want to see complete diffs at each step, use
+
+------------------------------------------------
+$ git log -p
+------------------------------------------------
+
+Managing branches
+-----------------
+
+A single git repository can maintain multiple branches of
+development.  To create a new branch named "experimental", use
+
+------------------------------------------------
+$ git branch experimental
+------------------------------------------------
+
+If you now run
+
+------------------------------------------------
+$ git branch
+------------------------------------------------
+
+you'll get a list of all existing branches:
+
+------------------------------------------------
+  experimental
+* master
+------------------------------------------------
+
+The "experimental" branch is the one you just created, and the
+"master" branch is a default branch that was created for you
+automatically.  The asterisk marks the branch you are currently on;
+type
+
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
+
+to switch to the experimental branch.  Now edit a file, commit the
+change, and switch back to the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
+
+Check that the change you made is no longer visible, since it was
+made on the experimental branch and you're back on the master branch.
+
+You can make a different change on the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
+
+at this point the two branches have diverged, with different changes
+made in each.  To merge the changes made in the two branches, run
+
+------------------------------------------------
+$ git pull . experimental
+------------------------------------------------
+
+If the changes don't conflict, you're done.  If there are conflicts,
+markers will be left in the problematic files showing the conflict;
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+will show this.  Once you've edited the files to resolve the
+conflicts,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will commit the result of the merge. Finally,
+
+------------------------------------------------
+$ gitk
+------------------------------------------------
+
+will show a nice graphical representation of the resulting history.
+
+If you develop on a branch crazy-idea, then regret it, you can always
+delete the branch with
+
+-------------------------------------
+$ git branch -D crazy-idea
+-------------------------------------
+
+Branches are cheap and easy, so this is a good way to try something
+out.
+
+Using git for collaboration
+---------------------------
+
+Suppose that Alice has started a new project with a git repository in
+/home/alice/project, and that Bob, who has a home directory on the
+same machine, wants to contribute.
+
+Bob begins with:
+
+------------------------------------------------
+$ git clone /home/alice/project myrepo
+------------------------------------------------
+
+This creates a new directory "myrepo" containing a clone of Alice's
+repository.  The clone is on an equal footing with the original
+project, possessing its own copy of the original project's history.
+
+Bob then makes some changes and commits them:
+
+------------------------------------------------
+(edit files)
+$ git commit -a
+(repeat as necessary)
+------------------------------------------------
+
+When he's ready, he tells Alice to pull changes from the repository
+at /home/bob/myrepo.  She does this with:
+
+------------------------------------------------
+$ cd /home/alice/project
+$ git pull /home/bob/myrepo
+------------------------------------------------
+
+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 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.
+
+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:
+
+-------------------------------------
+$ git fetch /home/bob/myrepo master:bob-incoming
+-------------------------------------
+
+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
+
+-------------------------------------
+$ git log -p master..bob-incoming
+-------------------------------------
+
+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:
+
+-------------------------------------
+$ git checkout master
+$ git pull . bob-incoming
+-------------------------------------
+
+The last command is a pull from the "bob-incoming" branch in Alice's
+own repository.
+
+Later, Bob can update his repo with Alice's latest changes using
+
+-------------------------------------
+$ 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:
+
+-------------------------------------
+$ git branch
+* master
+  origin
+-------------------------------------
+
+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.
+
+If Bob later decides to work from a different host, he can still
+perform clones and pulls using the ssh protocol:
+
+-------------------------------------
+$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
+
+Alternatively, git has a native protocol, or can use rsync or http;
+see gitlink:git-pull[1] for details.
+
+Git can also be used in a CVS-like mode, with a central repository
+that various users push changes to; see gitlink:git-push[1] and
+link:cvs-migration.html[git for CVS users].
+
+Exploring history
+-----------------
+
+Git history is represented as a series of interrelated commits.  We
+have already seen that the git log command can list those commits.
+Note that first line of each git log entry also gives a name for the
+commit:
+
+-------------------------------------
+$ git log
+commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+Author: Junio C Hamano <junkio@cox.net>
+Date:   Tue May 16 17:18:22 2006 -0700
+
+    merge-base: Clarify the comments on post processing.
+-------------------------------------
+
+We can give this name to git show to see the details about this
+commit.
+
+-------------------------------------
+$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+-------------------------------------
+
+But there other ways to refer to commits.  You can use any initial
+part of the name that is long enough to uniquely identify the commit:
+
+-------------------------------------
+$ git show c82a22c39c  # the first few characters of the name are
+                       # usually enough
+$ 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:
+
+-------------------------------------
+$ git show HEAD^  # to see the parent of HEAD
+$ git show HEAD^^ # to see the grandparent of HEAD
+$ git show HEAD~4 # to see the great-great grandparent of HEAD
+-------------------------------------
+
+Note that merge commits may have more than one parent:
+
+-------------------------------------
+$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
+$ git show HEAD^2 # show the second parent of HEAD
+-------------------------------------
+
+You can also give commits names of your own; after running
+
+-------------------------------------
+$ git-tag v2.5 1b2e1d63ff
+-------------------------------------
+
+you can refer to 1b2e1d63ff by the name "v2.5".  If you intend to
+share this name with other people (for example, to identify a release
+version), you should create a "tag" object, and perhaps sign it; see
+gitlink:git-tag[1] for details.
+
+Any git command that needs to know a commit can take any of these
+names.  For example:
+
+-------------------------------------
+$ git diff v2.5 HEAD    # compare the current HEAD to v2.5
+$ git branch stable v2.5 # start a new branch named "stable" based
+                        # at v2.5
+$ git reset --hard HEAD^ # reset your current branch and working
+                        # directory its state at HEAD^
+-------------------------------------
+
+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.)
+
+The git grep command can search for strings in any version of your
+project, so
+
+-------------------------------------
+$ git grep "hello" v2.5
+-------------------------------------
+
+searches for all occurrences of "hello" in v2.5.
+
+If you leave out the commit name, git grep will search any of the
+files it manages in your current directory.  So
+
+-------------------------------------
+$ git grep "hello"
+-------------------------------------
+
+is a quick way to search just the files that are tracked by git.
+
+Many git commands also take sets of commits, which can be specified
+in a number of ways.  Here are some examples with git log:
+
+-------------------------------------
+$ git log v2.5..v2.6            # commits between v2.5 and v2.6
+$ git log v2.5..                # commits since v2.5
+$ git log --since="2 weeks ago" # commits from the last 2 weeks
+$ git log v2.5.. Makefile       # commits since v2.5 which modify
+                               # Makefile
+-------------------------------------
+
+You can also give git log a "range" of commits where the first is not
+necessarily an ancestor of the second; for example, if the tips of
+the branches "stable-release" and "master" diverged from a common
+commit some time ago, then
+
+-------------------------------------
+$ git log stable..experimental
+-------------------------------------
+
+will list commits made in the experimental branch but not in the
+stable branch, while
+
+-------------------------------------
+$ git log experimental..stable
+-------------------------------------
+
+will show the list of commits made on the stable branch but not
+the experimental branch.
+
+The "git log" command has a weakness: it must present commits in a
+list.  When the history has lines of development that diverged and
+then merged back together, the order in which "git log" presents
+those commits is meaningless.
+
+Most projects with multiple contributors (such as the linux kernel,
+or git itself) have frequent merges, and gitk does a better job of
+visualizing their history.  For example,
+
+-------------------------------------
+$ gitk --since="2 weeks ago" drivers/
+-------------------------------------
+
+allows you to browse any commits from the last 2 weeks of commits
+that modified files under the "drivers" directory.  (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
+
+Finally, most commands that take filenames will optionally allow you
+to precede any filename by a commit, to specify a particular version
+of the file:
+
+-------------------------------------
+$ git diff v2.5:Makefile HEAD:Makefile.in
+-------------------------------------
+
+You can also use "git cat-file -p" to see any such file:
+
+-------------------------------------
+$ git cat-file -p v2.5:Makefile
+-------------------------------------
+
+Next Steps
+----------
+
+This tutorial should be enough to perform basic distributed revision
+control for your projects.  However, to fully understand the depth
+and power of git you need to understand two simple ideas on which it
+is based:
+
+  * The object database is the rather elegant system used to
+    store the history of your project--files, directories, and
+    commits.
+
+  * The index file is a cache of the state of a directory tree,
+    used to create commits, check out working directories, and
+    hold the various trees involved in a merge.
+
+link:tutorial-2.html[Part two of this tutorial] explains the object
+database, the index file, and a few other odds and ends that you'll
+need to make the most of git.
+
+If you don't want to consider with that right away, a few other
+digressions that may be interesting at this point are:
+
+  * gitlink:git-format-patch[1], gitlink:git-am[1]: These convert
+    series of git commits into emailed patches, and vice versa,
+    useful for projects such as the linux kernel which rely heavily
+    on emailed patches.
+
+  * gitlink:git-bisect[1]: When there is a regression in your
+    project, one way to track down the bug is by searching through
+    the history to find the exact commit that's to blame.  Git bisect
+    can help you perform a binary search for that commit.  It is
+    smart enough to perform a close-to-optimal search even in the
+    case of complex non-linear history with lots of merged branches.
+
+  * link:everyday.html[Everyday GIT with 20 Commands Or So]
+
+  * link:cvs-migration.html[git for CVS users].
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
new file mode 100644 (file)
index 0000000..7477413
--- /dev/null
@@ -0,0 +1,68 @@
+GIT URLS[[URLS]]
+----------------
+
+One of the following notations can be used
+to name the remote repository:
+
+===============================================================
+- rsync://host.xz/path/to/repo.git/
+- http://host.xz/path/to/repo.git/
+- https://host.xz/path/to/repo.git/
+- git://host.xz/path/to/repo.git/
+- git://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/path/to/repo.git/
+- ssh://host.xz/~user/path/to/repo.git/
+- ssh://host.xz/~/path/to/repo.git
+===============================================================
+
+SSH Is the default transport protocol and also supports an
+scp-like syntax.  Both syntaxes support username expansion,
+as does the native git protocol. The following three are
+identical to the last three above, respectively:
+
+===============================================================
+- host.xz:/path/to/repo.git/
+- host.xz:~user/path/to/repo.git/
+- host.xz:path/to/repo.git
+===============================================================
+
+To sync with a local directory, use:
+
+===============================================================
+- /path/to/repo.git/
+===============================================================
+
+REMOTES
+-------
+
+In addition to the above, as a short-hand, the name of a
+file in `$GIT_DIR/remotes` directory can be given; the
+named file should be in the following format:
+
+       URL: one of the above URL format
+       Push: <refspec>
+       Pull: <refspec>
+
+Then such a short-hand is specified in place of
+<repository> without <refspec> parameters on the command
+line, <refspec> specified on `Push:` lines or `Pull:`
+lines are used for `git-push` and `git-fetch`/`git-pull`,
+respectively.  Multiple `Push:` and and `Pull:` lines may
+be specified for additional branch mappings.
+
+The name of a file in `$GIT_DIR/branches` directory can be
+specified as an older notation short-hand; the named
+file should contain a single line, a URL in one of the
+above formats, optionally followed by a hash `#` and the
+name of remote head (URL fragment notation).
+`$GIT_DIR/branches/<remote>` file that stores a <url>
+without the fragment is equivalent to have this in the
+corresponding file in the `$GIT_DIR/remotes/` directory.
+
+       URL: <url>
+       Pull: refs/heads/master:<remote>
+
+while having `<url>#<head>` is equivalent to
+
+       URL: <url>
+       Pull: refs/heads/<head>:<remote>
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
new file mode 100755 (executable)
index 0000000..a461518
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+GVF=GIT-VERSION-FILE
+DEF_VER=v1.3.GIT
+
+# First try git-describe, then see if there is a version file
+# (included in release tarballs), then default
+if VN=$(git describe --abbrev=4 HEAD 2>/dev/null); then
+       VN=$(echo "$VN" | sed -e 's/-/./g');
+elif test -f version
+then
+       VN=$(cat version) || VN="$DEF_VER"
+else
+       VN="$DEF_VER"
+fi
+
+VN=$(expr "$VN" : v*'\(.*\)')
+
+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/^GIT_VERSION = //' <$GVF)
+else
+       VC=unset
+fi
+test "$VN" = "$VC" || {
+       echo >&2 "GIT_VERSION = $VN"
+       echo "GIT_VERSION = $VN" >$GVF
+}
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..63af8ec
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,114 @@
+
+               Git installation
+
+Normally you can just do "make" followed by "make install", and that
+will install the git programs in your own ~/bin/ directory.  If you want
+to do a global install, you can do
+
+       $ make prefix=/usr all doc ;# as yourself
+       # make prefix=/usr install install-doc ;# as root
+
+(or prefix=/usr/local, of course).  Just like any program suite
+that uses $prefix, the built results have some paths encoded,
+which are derived from $prefix, so "make all; make prefix=/usr
+install" would not work.
+
+Issues of note:
+
+ - git normally installs a helper script wrapper called "git", which
+   conflicts with a similarly named "GNU interactive tools" program.
+
+   Tough.  Either don't use the wrapper script, or delete the old GNU
+   interactive tools.  None of the core git stuff needs the wrapper,
+   it's just a convenient shorthand and while it is documented in some
+   places, you can always replace "git commit" with "git-commit"
+   instead. 
+
+   But let's face it, most of us don't have GNU interactive tools, and
+   even if we had it, we wouldn't know what it does.  I don't think it
+   has been actively developed since 1997, and people have moved over to
+   graphical file managers.
+
+ - Git is reasonably self-sufficient, but does depend on a few external
+   programs and libraries:
+
+       - "zlib", the compression library. Git won't build without it.
+
+       - "openssl".  The git-rev-list program uses bignum support from
+         openssl, and unless you specify otherwise, you'll also get the
+         SHA1 library from here.
+
+         If you don't have openssl, you can use one of the SHA1 libraries
+         that come with git (git includes the one from Mozilla, and has
+         its own PowerPC and ARM optimized ones too - see the Makefile).
+
+       - "libcurl" and "curl" executable.  git-http-fetch and
+         git-fetch use them.  If you do not use http
+         transfer, you are probabaly OK if you do not have
+         them.
+
+       - 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
+
+       - "ssh" is used to push and pull over the net
+
+       - "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
+   necessary libraries at unusual locations.  Please look at the
+   top of the Makefile to see what can be adjusted for your needs.
+   You can place local settings in config.mak and the Makefile
+   will include them.  Note that config.mak is not distributed;
+   the name is reserved for local settings.
+
+ - To build and install documentation suite, you need to have the
+   asciidoc/xmlto toolchain.  Alternatively, pre-formatted
+   documentation are available in "html" and "man" branches of the git
+   repository itself.  For example, you could:
+
+       $ mkdir manual && cd manual
+       $ git init-db
+       $ git clone-pack git://git.kernel.org/pub/scm/git/git.git man html |
+         while read a b
+         do
+           echo $a >.git/$b
+         done
+       $ cp .git/refs/heads/man .git/refs/heads/master
+       $ git checkout
+
+   to checkout the pre-built man pages.  Also in this repository:
+
+       $ git checkout html
+
+   would instead give you a copy of what you see at:
+
+       http://www.kernel.org/pub/software/scm/git/docs/
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..265a716
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,736 @@
+# The default target of this Makefile is...
+all:
+
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
+#
+# Define NO_CURL if you do not have curl installed.  git-http-pull and
+# git-http-push are not built, and you cannot use http:// and https://
+# transports.
+#
+# Define CURLDIR=/foo/bar if your curl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+# Define NO_EXPAT if you do not have expat installed.  git-http-push is
+# not built, and you cannot push using http:// and https:// transports.
+#
+# Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent.
+#
+# Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks
+# d_type in struct dirent (latest Cygwin -- will be fixed soonish).
+#
+# Define NO_STRCASESTR if you don't have strcasestr.
+#
+# Define NO_SETENV if you don't have setenv in the C library.
+#
+# 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 PPC_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for PowerPC.
+#
+# Define ARM_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for ARM.
+#
+# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
+#
+# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
+#
+# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
+# Patrick Mauritz).
+#
+# 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_IPV6 if you lack IPv6 support and getaddrinfo().
+#
+# Define NO_SOCKADDR_STORAGE if your platform does not have struct
+# sockaddr_storage.
+#
+# 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 NO_PYTHON if you want to loose all benefits of the recursive merge.
+#
+# 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
+# randomly break unless your underlying filesystem supports those sub-second
+# times (my ext3 doesn't).
+
+# Define USE_STDEV below if you want git to care about the underlying device
+# change being considered an inode change from the update-cache perspective.
+
+GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       @$(SHELL_PATH) ./GIT-VERSION-GEN
+-include GIT-VERSION-FILE
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
+uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
+
+# CFLAGS and LDFLAGS are for the users to override from the command line.
+
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+ALL_CFLAGS = $(CFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+STRIP ?= strip
+
+prefix = $(HOME)
+bindir = $(prefix)/bin
+gitexecdir = $(bindir)
+template_dir = $(prefix)/share/git-core/templates/
+GIT_PYTHON_DIR = $(prefix)/share/git-core/python
+# DESTDIR=
+
+CC = gcc
+AR = ar
+TAR = tar
+INSTALL = install
+RPMBUILD = rpmbuild
+
+# sparse is architecture-neutral, which means that we need to tell it
+# explicitly what architecture to check for. Fix this up for yours..
+SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
+
+
+
+### --- END CONFIGURATION SECTION ---
+
+SCRIPT_SH = \
+       git-bisect.sh git-branch.sh git-checkout.sh \
+       git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
+       git-fetch.sh \
+       git-ls-remote.sh \
+       git-merge-one-file.sh git-parse-remote.sh \
+       git-prune.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-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 \
+       git-merge-resolve.sh git-merge-ours.sh \
+       git-lost-found.sh git-quiltimport.sh
+
+SCRIPT_PERL = \
+       git-archimport.perl git-cvsimport.perl git-relink.perl \
+       git-shortlog.perl git-fmt-merge-msg.perl git-rerere.perl \
+       git-annotate.perl git-cvsserver.perl \
+       git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \
+       git-send-email.perl
+
+SCRIPT_PYTHON = \
+       git-merge-recursive.py
+
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
+         $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
+         git-cherry-pick git-status
+
+# The ones that do not have to link with lcrypto, lz nor xdiff.
+SIMPLE_PROGRAMS = \
+       git-get-tar-commit-id$X git-mailsplit$X \
+       git-stripspace$X git-daemon$X
+
+# ... and all the rest that could be moved out of bindir to gitexecdir
+PROGRAMS = \
+       git-checkout-index$X git-clone-pack$X \
+       git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
+       git-hash-object$X git-index-pack$X git-local-fetch$X \
+       git-mailinfo$X git-merge-base$X \
+       git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \
+       git-peek-remote$X git-prune-packed$X git-receive-pack$X \
+       git-send-pack$X git-shell$X \
+       git-show-index$X git-ssh-fetch$X \
+       git-ssh-upload$X git-unpack-file$X \
+       git-unpack-objects$X git-update-index$X git-update-server-info$X \
+       git-upload-pack$X git-verify-pack$X git-write-tree$X \
+       git-update-ref$X git-symbolic-ref$X \
+       git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
+       git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
+
+BUILT_INS = git-log$X git-whatchanged$X git-show$X \
+       git-count-objects$X git-diff$X git-push$X \
+       git-grep$X git-add$X git-rm$X git-rev-list$X \
+       git-check-ref-format$X git-rev-parse$X \
+       git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \
+       git-ls-files$X git-ls-tree$X \
+       git-read-tree$X git-commit-tree$X \
+       git-apply$X git-show-branch$X git-diff-files$X \
+       git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X
+
+# what 'all' will build and 'install' will install, in gitexecdir
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
+
+# Backward compatibility -- to be removed after 1.0
+PROGRAMS += git-ssh-pull$X git-ssh-push$X
+
+# Set paths to tools early so that they can be used for version tests.
+ifndef SHELL_PATH
+       SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+       PERL_PATH = /usr/bin/perl
+endif
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
+
+PYMODULES = \
+       gitMergeCommon.py
+
+LIB_FILE=libgit.a
+XDIFF_LIB=xdiff/lib.a
+
+LIB_H = \
+       blob.h cache.h commit.h csum-file.h delta.h \
+       diff.h object.h pack.h pkt-line.h quote.h refs.h \
+       run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
+       tree-walk.h log-tree.h dir.h
+
+DIFF_OBJS = \
+       diff.o diff-lib.o diffcore-break.o diffcore-order.o \
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
+       diffcore-delta.o log-tree.o
+
+LIB_OBJS = \
+       blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
+       date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \
+       object.o pack-check.o patch-delta.o path.o pkt-line.o \
+       quote.o read-cache.o refs.o run-command.o dir.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 \
+       fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
+       $(DIFF_OBJS)
+
+BUILTIN_OBJS = \
+       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
+       builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \
+       builtin-rm.o builtin-init-db.o builtin-rev-parse.o \
+       builtin-tar-tree.o builtin-upload-tar.o \
+       builtin-ls-files.o builtin-ls-tree.o \
+       builtin-read-tree.o builtin-commit-tree.o \
+       builtin-apply.o builtin-show-branch.o builtin-diff-files.o \
+       builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \
+       builtin-cat-file.o
+
+GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
+LIBS = $(GITLIBS) -lz
+
+#
+# Platform specific tweaks
+#
+
+# We choose to avoid "if .. else if .. else .. endif endif"
+# because maintaining the nesting to match is a pain.  If
+# we had "elif" things would have been much nicer...
+
+ifeq ($(uname_S),Darwin)
+       NEEDS_SSL_WITH_CRYPTO = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       ## fink
+       ifeq ($(shell test -d /sw/lib && echo y),y)
+               ALL_CFLAGS += -I/sw/include
+               ALL_LDFLAGS += -L/sw/lib
+       endif
+       ## darwinports
+       ifeq ($(shell test -d /opt/local/lib && echo y),y)
+               ALL_CFLAGS += -I/opt/local/include
+               ALL_LDFLAGS += -L/opt/local/lib
+       endif
+endif
+ifeq ($(uname_S),SunOS)
+       NEEDS_SOCKET = YesPlease
+       NEEDS_NSL = YesPlease
+       SHELL_PATH = /bin/bash
+       NO_STRCASESTR = YesPlease
+       ifeq ($(uname_R),5.8)
+               NEEDS_LIBICONV = YesPlease
+               NO_UNSETENV = YesPlease
+               NO_SETENV = YesPlease
+       endif
+       ifeq ($(uname_R),5.9)
+               NO_UNSETENV = YesPlease
+               NO_SETENV = YesPlease
+       endif
+       INSTALL = ginstall
+       TAR = gtar
+       ALL_CFLAGS += -D__EXTENSIONS__
+endif
+ifeq ($(uname_O),Cygwin)
+       NO_D_TYPE_IN_DIRENT = YesPlease
+       NO_D_INO_IN_DIRENT = YesPlease
+       NO_STRCASESTR = YesPlease
+       NO_SYMLINK_HEAD = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       # 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
+       NO_IPV6 = YesPlease
+       X = .exe
+endif
+ifeq ($(uname_S),FreeBSD)
+       NEEDS_LIBICONV = YesPlease
+       ALL_CFLAGS += -I/usr/local/include
+       ALL_LDFLAGS += -L/usr/local/lib
+endif
+ifeq ($(uname_S),OpenBSD)
+       NO_STRCASESTR = YesPlease
+       NEEDS_LIBICONV = YesPlease
+       ALL_CFLAGS += -I/usr/local/include
+       ALL_LDFLAGS += -L/usr/local/lib
+endif
+ifeq ($(uname_S),NetBSD)
+       ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
+               NEEDS_LIBICONV = YesPlease
+       endif
+       ALL_CFLAGS += -I/usr/pkg/include
+       ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+endif
+ifeq ($(uname_S),AIX)
+       NO_STRCASESTR=YesPlease
+       NEEDS_LIBICONV=YesPlease
+endif
+ifeq ($(uname_S),IRIX64)
+       NO_IPV6=YesPlease
+       NO_SETENV=YesPlease
+       NO_STRCASESTR=YesPlease
+       NO_SOCKADDR_STORAGE=YesPlease
+       SHELL_PATH=/usr/gnu/bin/bash
+       ALL_CFLAGS += -DPATH_MAX=1024
+       # for now, build 32-bit version
+       ALL_LDFLAGS += -L/usr/lib32
+endif
+ifneq (,$(findstring arm,$(uname_M)))
+       ARM_SHA1 = YesPlease
+endif
+
+-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
+               endif
+       endif
+endif
+
+ifndef NO_CURL
+       ifdef CURLDIR
+               # This is still problematic -- gcc does not always want -R.
+               ALL_CFLAGS += -I$(CURLDIR)/include
+               CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
+       else
+               CURL_LIBCURL = -lcurl
+       endif
+       PROGRAMS += git-http-fetch$X
+       curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)
+       ifeq "$(curl_check)" "070908"
+               ifndef NO_EXPAT
+                       PROGRAMS += git-http-push$X
+               endif
+       endif
+       ifndef NO_EXPAT
+               EXPAT_LIBEXPAT = -lexpat
+       endif
+endif
+
+ifndef NO_OPENSSL
+       OPENSSL_LIBSSL = -lssl
+       ifdef OPENSSLDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               ALL_CFLAGS += -I$(OPENSSLDIR)/include
+               OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
+       else
+               OPENSSL_LINK =
+       endif
+else
+       ALL_CFLAGS += -DNO_OPENSSL
+       MOZILLA_SHA1 = 1
+       OPENSSL_LIBSSL =
+endif
+ifdef NEEDS_SSL_WITH_CRYPTO
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
+else
+       LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
+endif
+ifdef NEEDS_LIBICONV
+       ifdef ICONVDIR
+               # Again this may be problematic -- gcc does not always want -R.
+               ALL_CFLAGS += -I$(ICONVDIR)/include
+               ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
+       else
+               ICONV_LINK =
+       endif
+       LIB_4_ICONV = $(ICONV_LINK) -liconv
+else
+       LIB_4_ICONV =
+endif
+ifdef NEEDS_SOCKET
+       LIBS += -lsocket
+       SIMPLE_LIB += -lsocket
+endif
+ifdef NEEDS_NSL
+       LIBS += -lnsl
+       SIMPLE_LIB += -lnsl
+endif
+ifdef NO_D_TYPE_IN_DIRENT
+       ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT
+endif
+ifdef NO_D_INO_IN_DIRENT
+       ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
+endif
+ifdef NO_SYMLINK_HEAD
+       ALL_CFLAGS += -DNO_SYMLINK_HEAD
+endif
+ifdef NO_STRCASESTR
+       COMPAT_CFLAGS += -DNO_STRCASESTR
+       COMPAT_OBJS += compat/strcasestr.o
+endif
+ifdef NO_SETENV
+       COMPAT_CFLAGS += -DNO_SETENV
+       COMPAT_OBJS += compat/setenv.o
+endif
+ifdef NO_SETENV
+       COMPAT_CFLAGS += -DNO_UNSETENV
+       COMPAT_OBJS += compat/unsetenv.o
+endif
+ifdef NO_MMAP
+       COMPAT_CFLAGS += -DNO_MMAP
+       COMPAT_OBJS += compat/mmap.o
+endif
+ifdef NO_IPV6
+       ALL_CFLAGS += -DNO_IPV6
+endif
+ifdef NO_SOCKADDR_STORAGE
+ifdef NO_IPV6
+       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in
+else
+       ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6
+endif
+endif
+ifdef NO_INET_NTOP
+       LIB_OBJS += compat/inet_ntop.o
+endif
+
+ifdef NO_ICONV
+       ALL_CFLAGS += -DNO_ICONV
+endif
+
+ifdef PPC_SHA1
+       SHA1_HEADER = "ppc/sha1.h"
+       LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
+else
+ifdef ARM_SHA1
+       SHA1_HEADER = "arm/sha1.h"
+       LIB_OBJS += arm/sha1.o arm/sha1_arm.o
+else
+ifdef MOZILLA_SHA1
+       SHA1_HEADER = "mozilla-sha1/sha1.h"
+       LIB_OBJS += mozilla-sha1/sha1.o
+else
+       SHA1_HEADER = <openssl/sha.h>
+       LIBS += $(LIB_4_CRYPTO)
+endif
+endif
+endif
+ifdef NO_ACCURATE_DIFF
+       ALL_CFLAGS += -DNO_ACCURATE_DIFF
+endif
+
+# Shell quote (do not use $(call) to accomodate ancient setups);
+
+SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
+
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
+template_dir_SQ = $(subst ','\'',$(template_dir))
+
+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))
+
+ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+ALL_CFLAGS += -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"'
+LIB_OBJS += $(COMPAT_OBJS)
+export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+### Build rules
+
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk
+
+all:
+       $(MAKE) -C templates
+
+strip: $(PROGRAMS) git$X
+       $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
+
+git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS)
+       $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+               $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
+               $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
+
+builtin-help.o: common-cmds.h
+
+$(BUILT_INS): git$X
+       rm -f $@ && ln git$X $@
+
+common-cmds.h: Documentation/git-*.txt
+       ./generate-cmdlist.sh > $@+
+       mv $@+ $@
+
+$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
+       rm -f $@ $@+
+       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' \
+           $@.sh >$@+
+       chmod +x $@+
+       mv $@+ $@
+
+$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+       rm -f $@ $@+
+       sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+           $@.perl >$@+
+       chmod +x $@+
+       mv $@+ $@
+
+$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
+       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 $@+ $@
+
+git-status: git-commit
+       cp $< $@+
+       mv $@+ $@
+
+# These can record GIT_VERSION
+git$X git.spec \
+       $(patsubst %.sh,%,$(SCRIPT_SH)) \
+       $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+       $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
+       : GIT-VERSION-FILE
+
+%.o: %.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) $<
+%.o: %.S
+       $(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+exec_cmd.o: exec_cmd.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
+
+http.o: http.c
+       $(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
+
+ifdef NO_EXPAT
+http-fetch.o: http-fetch.c http.h
+       $(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_EXPAT $<
+endif
+
+git-%$X: %.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
+$(SIMPLE_PROGRAMS) : $(LIB_FILE)
+$(SIMPLE_PROGRAMS) : git-%$X : %.o
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIB_FILE) $(SIMPLE_LIB)
+
+git-mailinfo$X: mailinfo.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIB_FILE) $(SIMPLE_LIB) $(LIB_4_ICONV)
+
+git-local-fetch$X: fetch.o
+git-ssh-fetch$X: rsh.o fetch.o
+git-ssh-upload$X: rsh.o
+git-ssh-pull$X: rsh.o fetch.o
+git-ssh-push$X: rsh.o
+
+git-imap-send$X: imap-send.o $(LIB_FILE)
+
+http.o http-fetch.o http-push.o: http.h
+git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+
+git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+
+$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
+$(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
+$(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_LIB): $(XDIFF_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+
+
+doc:
+       $(MAKE) -C Documentation all
+
+TAGS:
+       rm -f TAGS
+       find . -name '*.[hcS]' -print | xargs etags -a
+
+tags:
+       rm -f tags
+       find . -name '*.[hcS]' -print | xargs ctags -a
+
+### Testing rules
+
+# GNU make supports exporting all variables by "export" without parameters.
+# However, the environment gets quite big, and some programs have problems
+# with that.
+
+export NO_PYTHON
+
+test: all
+       $(MAKE) -C t/ all
+
+test-date$X: test-date.c date.o ctype.o
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
+
+test-delta$X: test-delta.c diff-delta.o patch-delta.o
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+
+test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
+check:
+       for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
+
+
+
+### Installation rules
+
+install: all
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
+       $(MAKE) -C templates install
+       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
+       then \
+               ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
+                       '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' || \
+               cp '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
+                       '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X'; \
+       fi
+       $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
+
+install-doc:
+       $(MAKE) -C Documentation install
+
+
+
+
+### Maintainer's dist rules
+
+git.spec: git.spec.in
+       sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@+
+       mv $@+ $@
+
+GIT_TARNAME=git-$(GIT_VERSION)
+dist: git.spec git-tar-tree
+       ./git-tar-tree HEAD $(GIT_TARNAME) > $(GIT_TARNAME).tar
+       @mkdir -p $(GIT_TARNAME)
+       @cp git.spec $(GIT_TARNAME)
+       @echo $(GIT_VERSION) > $(GIT_TARNAME)/version
+       $(TAR) rf $(GIT_TARNAME).tar \
+               $(GIT_TARNAME)/git.spec $(GIT_TARNAME)/version
+       @rm -rf $(GIT_TARNAME)
+       gzip -f -9 $(GIT_TARNAME).tar
+
+rpm: dist
+       $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
+
+htmldocs = git-htmldocs-$(GIT_VERSION)
+manpages = git-manpages-$(GIT_VERSION)
+dist-doc:
+       rm -fr .doc-tmp-dir
+       mkdir .doc-tmp-dir
+       $(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
+       cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
+       gzip -n -9 -f $(htmldocs).tar
+       :
+       rm -fr .doc-tmp-dir
+       mkdir .doc-tmp-dir .doc-tmp-dir/man1 .doc-tmp-dir/man7
+       $(MAKE) -C Documentation DESTDIR=./ \
+               man1=../.doc-tmp-dir/man1 \
+               man7=../.doc-tmp-dir/man7 \
+               install
+       cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
+       gzip -n -9 -f $(manpages).tar
+       rm -fr .doc-tmp-dir
+
+### Cleaning rules
+
+clean:
+       rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
+               $(LIB_FILE) $(XDIFF_LIB)
+       rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
+       rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
+       rm -rf $(GIT_TARNAME) .doc-tmp-dir
+       rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
+       rm -f $(htmldocs).tar $(manpages).tar
+       $(MAKE) -C Documentation/ clean
+       $(MAKE) -C templates clean
+       $(MAKE) -C t/ clean
+       rm -f GIT-VERSION-FILE
+
+.PHONY: all install clean strip
+.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags
+
+### Check documentation
+#
+check-docs::
+       @for v in $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk; \
+       do \
+               case "$$v" in \
+               git-merge-octopus | git-merge-ours | git-merge-recursive | \
+               git-merge-resolve | git-merge-stupid | \
+               git-ssh-pull | git-ssh-push ) continue ;; \
+               esac ; \
+               test -f "Documentation/$$v.txt" || \
+               echo "no doc: $$v"; \
+               grep -q "^gitlink:$$v\[[0-9]\]::" Documentation/git.txt || \
+               case "$$v" in \
+               git) ;; \
+               *) echo "no link: $$v";; \
+               esac ; \
+       done | sort
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..cee7e43
--- /dev/null
+++ b/README
@@ -0,0 +1,589 @@
+////////////////////////////////////////////////////////////////
+
+       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 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.
diff --git a/arm/sha1.c b/arm/sha1.c
new file mode 100644 (file)
index 0000000..11b1a04
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright:   (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created:     September 17, 2005
+ */
+
+#include <string.h>
+#include "sha1.h"
+
+extern void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+
+void SHA1_Init(SHA_CTX *c)
+{
+       c->len = 0;
+       c->hash[0] = 0x67452301;
+       c->hash[1] = 0xefcdab89;
+       c->hash[2] = 0x98badcfe;
+       c->hash[3] = 0x10325476;
+       c->hash[4] = 0xc3d2e1f0;
+}
+
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
+{
+       uint32_t workspace[80];
+       unsigned int partial;
+       unsigned long done;
+
+       partial = c->len & 0x3f;
+       c->len += n;
+       if ((partial + n) >= 64) {
+               if (partial) {
+                       done = 64 - partial;
+                       memcpy(c->buffer + partial, p, done);
+                       sha_transform(c->hash, c->buffer, workspace);
+                       partial = 0;
+               } else
+                       done = 0;
+               while (n >= done + 64) {
+                       sha_transform(c->hash, p + done, workspace);
+                       done += 64;
+               }
+       } else
+               done = 0;
+       if (n - done)
+               memcpy(c->buffer + partial, p + done, n - done);
+}
+
+void SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+       uint64_t bitlen;
+       uint32_t bitlen_hi, bitlen_lo; 
+       unsigned int i, offset, padlen;
+       unsigned char bits[8];
+       static const unsigned char padding[64] = { 0x80, };
+
+       bitlen = c->len << 3;
+       offset = c->len & 0x3f;
+       padlen = ((offset < 56) ? 56 : (64 + 56)) - offset;
+       SHA1_Update(c, padding, padlen);
+
+       bitlen_hi = bitlen >> 32;
+       bitlen_lo = bitlen & 0xffffffff;
+       bits[0] = bitlen_hi >> 24;
+       bits[1] = bitlen_hi >> 16;
+       bits[2] = bitlen_hi >> 8;
+       bits[3] = bitlen_hi;
+       bits[4] = bitlen_lo >> 24;
+       bits[5] = bitlen_lo >> 16;
+       bits[6] = bitlen_lo >> 8;
+       bits[7] = bitlen_lo;
+       SHA1_Update(c, bits, 8); 
+
+       for (i = 0; i < 5; i++) {
+               uint32_t v = c->hash[i];
+               hash[0] = v >> 24;
+               hash[1] = v >> 16;
+               hash[2] = v >> 8;
+               hash[3] = v;
+               hash += 4;
+       }
+}
diff --git a/arm/sha1.h b/arm/sha1.h
new file mode 100644 (file)
index 0000000..3952646
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright:  (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created:    September 17, 2005
+ */
+
+#include <stdint.h>
+
+typedef struct sha_context {
+       uint64_t len;
+       uint32_t hash[5];
+       unsigned char buffer[64];
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *c);
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+void SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S
new file mode 100644 (file)
index 0000000..da92d20
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ *  SHA transform optimized for ARM
+ *
+ *  Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org>
+ *  Created:   September 17, 2005
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ */
+
+       .text
+       .globl  sha_transform
+
+/*
+ * void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+ *
+ * note: the "data" pointer may be unaligned.
+ */
+
+sha_transform:
+
+       stmfd   sp!, {r4 - r8, lr}
+
+       @ for (i = 0; i < 16; i++)
+       @         W[i] = ntohl(((uint32_t *)data)[i]); */
+
+#ifdef __ARMEB__
+       mov     r4, r0
+       mov     r0, r2
+       mov     r2, #64
+       bl      memcpy
+       mov     r2, r0
+       mov     r0, r4
+#else
+       mov     r3, r2
+       mov     lr, #16
+1:     ldrb    r4, [r1], #1
+       ldrb    r5, [r1], #1
+       ldrb    r6, [r1], #1
+       ldrb    r7, [r1], #1
+       subs    lr, lr, #1
+       orr     r5, r5, r4, lsl #8
+       orr     r6, r6, r5, lsl #8
+       orr     r7, r7, r6, lsl #8
+       str     r7, [r3], #4
+       bne     1b
+#endif
+
+       @ for (i = 0; i < 64; i++)
+       @         W[i+16] = ror(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 31);
+
+       sub     r3, r2, #4
+       mov     lr, #64
+2:     ldr     r4, [r3, #4]!
+       subs    lr, lr, #1
+       ldr     r5, [r3, #8]
+       ldr     r6, [r3, #32]
+       ldr     r7, [r3, #52]
+       eor     r4, r4, r5
+       eor     r4, r4, r6
+       eor     r4, r4, r7
+       mov     r4, r4, ror #31
+       str     r4, [r3, #64]
+       bne     2b
+
+       /*
+        * The SHA functions are:
+        *
+        * f1(B,C,D) = (D ^ (B & (C ^ D)))
+        * f2(B,C,D) = (B ^ C ^ D)
+        * f3(B,C,D) = ((B & C) | (D & (B | C)))
+        *
+        * Then the sub-blocks are processed as follows:
+        *
+        * A' = ror(A, 27) + f(B,C,D) + E + K + *W++
+        * B' = A
+        * C' = ror(B, 2)
+        * D' = C
+        * E' = D
+        *
+        * We therefore unroll each loop 5 times to avoid register shuffling.
+        * Also the ror for C (and also D and E which are successivelyderived
+        * from it) is applied in place to cut on an additional mov insn for
+        * each round.
+        */
+
+       .macro  sha_f1, A, B, C, D, E
+       ldr     r3, [r2], #4
+       eor     ip, \C, \D
+       add     \E, r1, \E, ror #2
+       and     ip, \B, ip, ror #2
+       add     \E, \E, \A, ror #27
+       eor     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       add     \E, \E, ip
+       .endm
+
+       .macro  sha_f2, A, B, C, D, E
+       ldr     r3, [r2], #4
+       add     \E, r1, \E, ror #2
+       eor     ip, \B, \C, ror #2
+       add     \E, \E, \A, ror #27
+       eor     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       add     \E, \E, ip
+       .endm
+
+       .macro  sha_f3, A, B, C, D, E
+       ldr     r3, [r2], #4
+       add     \E, r1, \E, ror #2
+       orr     ip, \B, \C, ror #2
+       add     \E, \E, \A, ror #27
+       and     ip, ip, \D, ror #2
+       add     \E, \E, r3
+       and     r3, \B, \C, ror #2
+       orr     ip, ip, r3
+       add     \E, \E, ip
+       .endm
+
+       ldmia   r0, {r4 - r8}
+
+       mov     lr, #4
+       ldr     r1, .L_sha_K + 0
+
+       /* adjust initial values */
+       mov     r6, r6, ror #30
+       mov     r7, r7, ror #30
+       mov     r8, r8, ror #30
+
+3:     subs    lr, lr, #1
+       sha_f1  r4, r5, r6, r7, r8
+       sha_f1  r8, r4, r5, r6, r7
+       sha_f1  r7, r8, r4, r5, r6
+       sha_f1  r6, r7, r8, r4, r5
+       sha_f1  r5, r6, r7, r8, r4
+       bne     3b
+
+       ldr     r1, .L_sha_K + 4
+       mov     lr, #4
+
+4:     subs    lr, lr, #1
+       sha_f2  r4, r5, r6, r7, r8
+       sha_f2  r8, r4, r5, r6, r7
+       sha_f2  r7, r8, r4, r5, r6
+       sha_f2  r6, r7, r8, r4, r5
+       sha_f2  r5, r6, r7, r8, r4
+       bne     4b
+
+       ldr     r1, .L_sha_K + 8
+       mov     lr, #4
+
+5:     subs    lr, lr, #1
+       sha_f3  r4, r5, r6, r7, r8
+       sha_f3  r8, r4, r5, r6, r7
+       sha_f3  r7, r8, r4, r5, r6
+       sha_f3  r6, r7, r8, r4, r5
+       sha_f3  r5, r6, r7, r8, r4
+       bne     5b
+
+       ldr     r1, .L_sha_K + 12
+       mov     lr, #4
+
+6:     subs    lr, lr, #1
+       sha_f2  r4, r5, r6, r7, r8
+       sha_f2  r8, r4, r5, r6, r7
+       sha_f2  r7, r8, r4, r5, r6
+       sha_f2  r6, r7, r8, r4, r5
+       sha_f2  r5, r6, r7, r8, r4
+       bne     6b
+
+       ldmia   r0, {r1, r2, r3, ip, lr}
+       add     r4, r1, r4
+       add     r5, r2, r5
+       add     r6, r3, r6, ror #2
+       add     r7, ip, r7, ror #2
+       add     r8, lr, r8, ror #2
+       stmia   r0, {r4 - r8}
+
+       ldmfd   sp!, {r4 - r8, pc}
+
+.L_sha_K:
+       .word   0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6
+
diff --git a/base85.c b/base85.c
new file mode 100644 (file)
index 0000000..a9e97f8
--- /dev/null
+++ b/base85.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+
+#undef DEBUG_85
+
+#ifdef DEBUG_85
+#define say(a) fprintf(stderr, a)
+#define say1(a,b) fprintf(stderr, a, b)
+#define say2(a,b,c) fprintf(stderr, a, b, c)
+#else
+#define say(a) do {} while(0)
+#define say1(a,b) do {} while(0)
+#define say2(a,b,c) do {} while(0)
+#endif
+
+static const char en85[] = {
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+       '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',
+       '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',
+       '!', '#', '$', '%', '&', '(', ')', '*', '+', '-',
+       ';', '<', '=', '>', '?', '@', '^', '_', '`', '{',
+       '|', '}', '~'
+};
+
+static char de85[256];
+static void prep_base85(void)
+{
+       int i;
+       if (de85['Z'])
+               return;
+       for (i = 0; i < ARRAY_SIZE(en85); i++) {
+               int ch = en85[i];
+               de85[ch] = i + 1;
+       }
+}
+
+int decode_85(char *dst, char *buffer, int len)
+{
+       prep_base85();
+
+       say2("decode 85 <%.*s>", len/4*5, buffer);
+       while (len) {
+               unsigned acc = 0;
+               int de, cnt = 4;
+               unsigned char ch;
+               do {
+                       ch = *buffer++;
+                       de = de85[ch];
+                       if (--de < 0)
+                               return error("invalid base85 alphabet %c", ch);
+                       acc = acc * 85 + de;
+               } while (--cnt);
+               ch = *buffer++;
+               de = de85[ch];
+               if (--de < 0)
+                       return error("invalid base85 alphabet %c", ch);
+               /*
+                * Detect overflow.  The largest
+                * 5-letter possible is "|NsC0" to
+                * encode 0xffffffff, and "|NsC" gives
+                * 0x03030303 at this point (i.e.
+                * 0xffffffff = 0x03030303 * 85).
+                */
+               if (0x03030303 < acc ||
+                   0xffffffff - de < (acc *= 85))
+                       error("invalid base85 sequence %.5s", buffer-5);
+               acc += de;
+               say1(" %08x", acc);
+
+               cnt = (len < 4) ? len : 4;
+               len -= cnt;
+               do {
+                       acc = (acc << 8) | (acc >> 24);
+                       *dst++ = acc;
+               } while (--cnt);
+       }
+       say("\n");
+
+       return 0;
+}
+
+void encode_85(char *buf, unsigned char *data, int bytes)
+{
+       prep_base85();
+
+       say("encode 85");
+       while (bytes) {
+               unsigned acc = 0;
+               int cnt;
+               for (cnt = 24; cnt >= 0; cnt -= 8) {
+                       int ch = *data++;
+                       acc |= ch << cnt;
+                       if (--bytes == 0)
+                               break;
+               }
+               say1(" %08x", acc);
+               for (cnt = 4; cnt >= 0; cnt--) {
+                       int val = acc % 85;
+                       acc /= 85;
+                       buf[cnt] = en85[val];
+               }
+               buf += 5;
+       }
+       say("\n");
+
+       *buf = 0;
+}
+
+#ifdef DEBUG_85
+int main(int ac, char **av)
+{
+       char buf[1024];
+
+       if (!strcmp(av[1], "-e")) {
+               int len = strlen(av[2]);
+               encode_85(buf, av[2], len);
+               if (len <= 26) len = len + 'A' - 1;
+               else len = len + 'a' - 26 + 1;
+               printf("encoded: %c%s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-d")) {
+               int len = *av[2];
+               if ('A' <= len && len <= 'Z') len = len - 'A' + 1;
+               else len = len - 'a' + 26 + 1;
+               decode_85(buf, av[2]+1, len);
+               printf("decoded: %.*s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-t")) {
+               char t[4] = { -1,-1,-1,-1 };
+               encode_85(buf, t, 4);
+               printf("encoded: D%s\n", buf);
+               return 0;
+       }
+}
+#endif
diff --git a/blame.c b/blame.c
new file mode 100644 (file)
index 0000000..88bfec2
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,907 @@
+/*
+ * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
+ */
+
+#include <assert.h>
+#include <time.h>
+#include <sys/time.h>
+#include <math.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "xdiff-interface.h"
+
+#define DEBUG 0
+
+static const char blame_usage[] = "[-c] [-l] [--] file [commit]\n"
+       "  -c, --compability Use the same output mode as git-annotate (Default: off)\n"
+       "  -l, --long        Show long commit SHA1 (Default: off)\n"
+       "  -h, --help        This message";
+
+static struct commit **blame_lines;
+static int num_blame_lines;
+static char* blame_contents;
+static int blame_len;
+
+struct util_info {
+       int *line_map;
+       unsigned char sha1[20]; /* blob sha, not commit! */
+       char *buf;
+       unsigned long size;
+       int num_lines;
+       const char* pathname;
+
+       void* topo_data;
+};
+
+struct chunk {
+       int off1, len1; // ---
+       int off2, len2; // +++
+};
+
+struct patch {
+       struct chunk *chunks;
+       int num;
+};
+
+static void get_blob(struct commit *commit);
+
+/* Only used for statistics */
+static int num_get_patch = 0;
+static int num_commits = 0;
+static int patch_time = 0;
+
+struct blame_diff_state {
+       struct xdiff_emit_state xm;
+       struct patch *ret;
+};
+
+static void process_u0_diff(void *state_, char *line, unsigned long len)
+{
+       struct blame_diff_state *state = state_;
+       struct chunk *chunk;
+
+       if (len < 4 || line[0] != '@' || line[1] != '@')
+               return;
+
+       if (DEBUG)
+               printf("chunk line: %.*s", (int)len, line);
+       state->ret->num++;
+       state->ret->chunks = xrealloc(state->ret->chunks,
+                                     sizeof(struct chunk) * state->ret->num);
+       chunk = &state->ret->chunks[state->ret->num - 1];
+
+       assert(!strncmp(line, "@@ -", 4));
+
+       if (parse_hunk_header(line, len,
+                             &chunk->off1, &chunk->len1,
+                             &chunk->off2, &chunk->len2)) {
+               state->ret->num--;
+               return;
+       }
+
+       if (chunk->len1 == 0)
+               chunk->off1++;
+       if (chunk->len2 == 0)
+               chunk->off2++;
+
+       if (chunk->off1 > 0)
+               chunk->off1--;
+       if (chunk->off2 > 0)
+               chunk->off2--;
+
+       assert(chunk->off1 >= 0);
+       assert(chunk->off2 >= 0);
+}
+
+static struct patch *get_patch(struct commit *commit, struct commit *other)
+{
+       struct blame_diff_state state;
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       mmfile_t file_c, file_o;
+       xdemitcb_t ecb;
+       struct util_info *info_c = (struct util_info *)commit->object.util;
+       struct util_info *info_o = (struct util_info *)other->object.util;
+       struct timeval tv_start, tv_end;
+
+       get_blob(commit);
+       file_c.ptr = info_c->buf;
+       file_c.size = info_c->size;
+
+       get_blob(other);
+       file_o.ptr = info_o->buf;
+       file_o.size = info_o->size;
+
+       gettimeofday(&tv_start, NULL);
+
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 0;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = process_u0_diff;
+       state.ret = xmalloc(sizeof(struct patch));
+       state.ret->chunks = NULL;
+       state.ret->num = 0;
+
+       xdl_diff(&file_c, &file_o, &xpp, &xecfg, &ecb);
+
+       gettimeofday(&tv_end, NULL);
+       patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
+               tv_end.tv_usec - tv_start.tv_usec;
+
+       num_get_patch++;
+       return state.ret;
+}
+
+static void free_patch(struct patch *p)
+{
+       free(p->chunks);
+       free(p);
+}
+
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage);
+
+static unsigned char blob_sha1[20];
+static const char* blame_file;
+static int get_blob_sha1(struct tree *t, const char *pathname,
+                        unsigned char *sha1)
+{
+       int i;
+       const char *pathspec[2];
+       blame_file = pathname;
+       pathspec[0] = pathname;
+       pathspec[1] = NULL;
+       memset(blob_sha1, 0, sizeof(blob_sha1));
+       read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+       for (i = 0; i < 20; i++) {
+               if (blob_sha1[i] != 0)
+                       break;
+       }
+
+       if (i == 20)
+               return -1;
+
+       memcpy(sha1, blob_sha1, 20);
+       return 0;
+}
+
+static int get_blob_sha1_internal(const unsigned char *sha1, const char *base,
+                                 int baselen, const char *pathname,
+                                 unsigned mode, int stage)
+{
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       if (strncmp(blame_file, base, baselen) ||
+           strcmp(blame_file + baselen, pathname))
+               return -1;
+
+       memcpy(blob_sha1, sha1, 20);
+       return -1;
+}
+
+static void get_blob(struct commit *commit)
+{
+       struct util_info *info = commit->object.util;
+       char type[20];
+
+       if (info->buf)
+               return;
+
+       info->buf = read_sha1_file(info->sha1, type, &info->size);
+
+       assert(!strcmp(type, blob_type));
+}
+
+/* For debugging only */
+static void print_patch(struct patch *p)
+{
+       int i;
+       printf("Num chunks: %d\n", p->num);
+       for (i = 0; i < p->num; i++) {
+               printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
+                      p->chunks[i].off2, p->chunks[i].len2);
+       }
+}
+
+#if DEBUG
+/* For debugging only */
+static void print_map(struct commit *cmit, struct commit *other)
+{
+       struct util_info *util = cmit->object.util;
+       struct util_info *util2 = other->object.util;
+
+       int i;
+       int max =
+           util->num_lines >
+           util2->num_lines ? util->num_lines : util2->num_lines;
+       int num;
+
+       for (i = 0; i < max; i++) {
+               printf("i: %d ", i);
+               num = -1;
+
+               if (i < util->num_lines) {
+                       num = util->line_map[i];
+                       printf("%d\t", num);
+               } else
+                       printf("\t");
+
+               if (i < util2->num_lines) {
+                       int num2 = util2->line_map[i];
+                       printf("%d\t", num2);
+                       if (num != -1 && num2 != num)
+                               printf("---");
+               } else
+                       printf("\t");
+
+               printf("\n");
+       }
+}
+#endif
+
+// p is a patch from commit to other.
+static void fill_line_map(struct commit *commit, struct commit *other,
+                         struct patch *p)
+{
+       struct util_info *util = commit->object.util;
+       struct util_info *util2 = other->object.util;
+       int *map = util->line_map;
+       int *map2 = util2->line_map;
+       int cur_chunk = 0;
+       int i1, i2;
+
+       if (p->num && DEBUG)
+               print_patch(p);
+
+       if (DEBUG)
+               printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
+                      util2->num_lines);
+
+       for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
+               struct chunk *chunk = NULL;
+               if (cur_chunk < p->num)
+                       chunk = &p->chunks[cur_chunk];
+
+               if (chunk && chunk->off1 == i1) {
+                       if (DEBUG && i2 != chunk->off2)
+                               printf("i2: %d off2: %d\n", i2, chunk->off2);
+
+                       assert(i2 == chunk->off2);
+
+                       i1--;
+                       i2--;
+                       if (chunk->len1 > 0)
+                               i1 += chunk->len1;
+
+                       if (chunk->len2 > 0)
+                               i2 += chunk->len2;
+
+                       cur_chunk++;
+               } else {
+                       if (i2 >= util2->num_lines)
+                               break;
+
+                       if (map[i1] != map2[i2] && map[i1] != -1) {
+                               if (DEBUG)
+                                       printf("map: i1: %d %d %p i2: %d %d %p\n",
+                                              i1, map[i1],
+                                              i1 != -1 ? blame_lines[map[i1]] : NULL,
+                                              i2, map2[i2],
+                                              i2 != -1 ? blame_lines[map2[i2]] : NULL);
+                               if (map2[i2] != -1 &&
+                                   blame_lines[map[i1]] &&
+                                   !blame_lines[map2[i2]])
+                                       map[i1] = map2[i2];
+                       }
+
+                       if (map[i1] == -1 && map2[i2] != -1)
+                               map[i1] = map2[i2];
+               }
+
+               if (DEBUG > 1)
+                       printf("l1: %d l2: %d i1: %d i2: %d\n",
+                              map[i1], map2[i2], i1, i2);
+       }
+}
+
+static int map_line(struct commit *commit, int line)
+{
+       struct util_info *info = commit->object.util;
+       assert(line >= 0 && line < info->num_lines);
+       return info->line_map[line];
+}
+
+static struct util_info* get_util(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+
+       if (util)
+               return util;
+
+       util = xmalloc(sizeof(struct util_info));
+       util->buf = NULL;
+       util->size = 0;
+       util->line_map = NULL;
+       util->num_lines = -1;
+       util->pathname = NULL;
+       commit->object.util = util;
+       return util;
+}
+
+static int fill_util_info(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+
+       assert(util);
+       assert(util->pathname);
+
+       if (get_blob_sha1(commit->tree, util->pathname, util->sha1))
+               return 1;
+       else
+               return 0;
+}
+
+static void alloc_line_map(struct commit *commit)
+{
+       struct util_info *util = commit->object.util;
+       int i;
+
+       if (util->line_map)
+               return;
+
+       get_blob(commit);
+
+       util->num_lines = 0;
+       for (i = 0; i < util->size; i++) {
+               if (util->buf[i] == '\n')
+                       util->num_lines++;
+       }
+       if(util->buf[util->size - 1] != '\n')
+               util->num_lines++;
+
+       util->line_map = xmalloc(sizeof(int) * util->num_lines);
+
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = -1;
+}
+
+static void init_first_commit(struct commit* commit, const char* filename)
+{
+       struct util_info* util = commit->object.util;
+       int i;
+
+       util->pathname = filename;
+       if (fill_util_info(commit))
+               die("fill_util_info failed");
+
+       alloc_line_map(commit);
+
+       util = commit->object.util;
+
+       for (i = 0; i < util->num_lines; i++)
+               util->line_map[i] = i;
+}
+
+
+static void process_commits(struct rev_info *rev, const char *path,
+                           struct commit** initial)
+{
+       int i;
+       struct util_info* util;
+       int lines_left;
+       int *blame_p;
+       int *new_lines;
+       int new_lines_len;
+
+       struct commit* commit = get_revision(rev);
+       assert(commit);
+       init_first_commit(commit, path);
+
+       util = commit->object.util;
+       num_blame_lines = util->num_lines;
+       blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+       blame_contents = util->buf;
+       blame_len = util->size;
+
+       for (i = 0; i < num_blame_lines; i++)
+               blame_lines[i] = NULL;
+
+       lines_left = num_blame_lines;
+       blame_p = xmalloc(sizeof(int) * num_blame_lines);
+       new_lines = xmalloc(sizeof(int) * num_blame_lines);
+       do {
+               struct commit_list *parents;
+               int num_parents;
+               struct util_info *util;
+
+               if (DEBUG)
+                       printf("\nProcessing commit: %d %s\n", num_commits,
+                              sha1_to_hex(commit->object.sha1));
+
+               if (lines_left == 0)
+                       return;
+
+               num_commits++;
+               memset(blame_p, 0, sizeof(int) * num_blame_lines);
+               new_lines_len = 0;
+               num_parents = 0;
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next)
+                       num_parents++;
+
+               if(num_parents == 0)
+                       *initial = commit;
+
+               if (fill_util_info(commit))
+                       continue;
+
+               alloc_line_map(commit);
+               util = commit->object.util;
+
+               for (parents = commit->parents;
+                    parents != NULL; parents = parents->next) {
+                       struct commit *parent = parents->item;
+                       struct patch *patch;
+
+                       if (parse_commit(parent) < 0)
+                               die("parse_commit error");
+
+                       if (DEBUG)
+                               printf("parent: %s\n",
+                                      sha1_to_hex(parent->object.sha1));
+
+                       if (fill_util_info(parent)) {
+                               num_parents--;
+                               continue;
+                       }
+
+                       patch = get_patch(parent, commit);
+                        alloc_line_map(parent);
+                        fill_line_map(parent, commit, patch);
+
+                        for (i = 0; i < patch->num; i++) {
+                            int l;
+                            for (l = 0; l < patch->chunks[i].len2; l++) {
+                                int mapped_line =
+                                    map_line(commit, patch->chunks[i].off2 + l);
+                                if (mapped_line != -1) {
+                                    blame_p[mapped_line]++;
+                                    if (blame_p[mapped_line] == num_parents)
+                                        new_lines[new_lines_len++] = mapped_line;
+                                }
+                            }
+                       }
+                        free_patch(patch);
+               }
+
+               if (DEBUG)
+                       printf("parents: %d\n", num_parents);
+
+               for (i = 0; i < new_lines_len; i++) {
+                       int mapped_line = new_lines[i];
+                       if (blame_lines[mapped_line] == NULL) {
+                               blame_lines[mapped_line] = commit;
+                               lines_left--;
+                               if (DEBUG)
+                                       printf("blame: mapped: %d i: %d\n",
+                                              mapped_line, i);
+                       }
+               }
+       } while ((commit = get_revision(rev)) != NULL);
+}
+
+
+static int compare_tree_path(struct rev_info* revs,
+                            struct commit* c1, struct commit* c2)
+{
+       int ret;
+       const char* paths[2];
+       struct util_info* util = c2->object.util;
+       paths[0] = util->pathname;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
+                             &revs->pruning);
+       ret = rev_compare_tree(revs, c1->tree, c2->tree);
+       diff_tree_release_paths(&revs->pruning);
+       return ret;
+}
+
+
+static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
+                                  const char* path)
+{
+       int ret;
+       const char* paths[2];
+       paths[0] = path;
+       paths[1] = NULL;
+
+       diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
+                             &revs->pruning);
+       ret = rev_same_tree_as_empty(revs, t1);
+       diff_tree_release_paths(&revs->pruning);
+       return ret;
+}
+
+static const char* find_rename(struct commit* commit, struct commit* parent)
+{
+       struct util_info* cutil = commit->object.util;
+       struct diff_options diff_opts;
+       const char *paths[1];
+       int i;
+
+       if (DEBUG) {
+               printf("find_rename commit: %s ",
+                      sha1_to_hex(commit->object.sha1));
+               puts(sha1_to_hex(parent->object.sha1));
+       }
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("diff_setup_done failed");
+
+       diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1,
+                      "", &diff_opts);
+       diffcore_std(&diff_opts);
+
+       for (i = 0; i < diff_queued_diff.nr; i++) {
+               struct diff_filepair *p = diff_queued_diff.queue[i];
+
+               if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) {
+                       if (DEBUG)
+                               printf("rename %s -> %s\n", p->one->path, p->two->path);
+                       return p->two->path;
+               }
+       }
+
+       return 0;
+}
+
+static void simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *parent;
+
+       if (!commit->tree)
+               return;
+
+       if (!commit->parents) {
+               struct util_info* util = commit->object.util;
+               if (!same_tree_as_empty_path(revs, commit->tree,
+                                            util->pathname))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
+               struct commit *p = parent->item;
+
+               if (p->object.flags & UNINTERESTING) {
+                       pp = &parent->next;
+                       continue;
+               }
+
+               parse_commit(p);
+               switch (compare_tree_path(revs, p, commit)) {
+               case REV_TREE_SAME:
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       get_util(p)->pathname = get_util(commit)->pathname;
+                       return;
+
+               case REV_TREE_NEW:
+               {
+
+                       struct util_info* util = commit->object.util;
+                       if (revs->remove_empty_trees &&
+                           same_tree_as_empty_path(revs, p->tree,
+                                                   util->pathname)) {
+                               const char* new_name = find_rename(commit, p);
+                               if (new_name) {
+                                       struct util_info* putil = get_util(p);
+                                       if (!putil->pathname)
+                                               putil->pathname = strdup(new_name);
+                               } else {
+                                       *pp = parent->next;
+                                       continue;
+                               }
+                       }
+               }
+
+               /* fallthrough */
+               case REV_TREE_DIFFERENT:
+                       pp = &parent->next;
+                       if (!get_util(p)->pathname)
+                               get_util(p)->pathname =
+                                       get_util(commit)->pathname;
+                       continue;
+               }
+               die("bad tree compare for commit %s",
+                   sha1_to_hex(commit->object.sha1));
+       }
+       commit->object.flags |= TREECHANGE;
+}
+
+
+struct commit_info
+{
+       char* author;
+       char* author_mail;
+       unsigned long author_time;
+       char* author_tz;
+};
+
+static void get_commit_info(struct commit* commit, struct commit_info* ret)
+{
+       int len;
+       char* tmp;
+       static char author_buf[1024];
+
+       tmp = strstr(commit->buffer, "\nauthor ") + 8;
+       len = strchr(tmp, '\n') - tmp;
+       ret->author = author_buf;
+       memcpy(ret->author, tmp, len);
+
+       tmp = ret->author;
+       tmp += len;
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_tz = tmp+1;
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_time = strtoul(tmp, NULL, 10);
+
+       *tmp = 0;
+       while(*tmp != ' ')
+               tmp--;
+       ret->author_mail = tmp + 1;
+
+       *tmp = 0;
+}
+
+static const char* format_time(unsigned long time, const char* tz_str)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       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;
+}
+
+static void topo_setter(struct commit* c, void* data)
+{
+       struct util_info* util = c->object.util;
+       util->topo_data = data;
+}
+
+static void* topo_getter(struct commit* c)
+{
+       struct util_info* util = c->object.util;
+       return util->topo_data;
+}
+
+static int read_ancestry(const char *graft_file,
+                        unsigned char **start_sha1)
+{
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp)
+               return -1;
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               struct commit_graft *graft = read_graft_line(buf, len);
+               register_commit_graft(graft, 0);
+               if (!*start_sha1)
+                       *start_sha1 = graft->sha1;
+       }
+       fclose(fp);
+       return 0;
+}
+
+int main(int argc, const char **argv)
+{
+       int i;
+       struct commit *initial = NULL;
+       unsigned char sha1[20], *sha1_p = NULL;
+
+       const char *filename = NULL, *commit = NULL;
+       char filename_buf[256];
+       int sha1_len = 8;
+       int compability = 0;
+       int options = 1;
+       struct commit* start_commit;
+
+       const char* args[10];
+       struct rev_info rev;
+
+       struct commit_info ci;
+       const char *buf;
+       int max_digits;
+       int longest_file, longest_author;
+       int found_rename;
+
+       const char* prefix = setup_git_directory();
+       git_config(git_default_config);
+
+       for(i = 1; i < argc; i++) {
+               if(options) {
+                       if(!strcmp(argv[i], "-h") ||
+                          !strcmp(argv[i], "--help"))
+                               usage(blame_usage);
+                       else if(!strcmp(argv[i], "-l") ||
+                               !strcmp(argv[i], "--long")) {
+                               sha1_len = 40;
+                               continue;
+                       } else if(!strcmp(argv[i], "-c") ||
+                                 !strcmp(argv[i], "--compability")) {
+                               compability = 1;
+                               continue;
+                       } else if(!strcmp(argv[i], "-S")) {
+                               if (i + 1 < argc &&
+                                   !read_ancestry(argv[i + 1], &sha1_p)) {
+                                       compability = 1;
+                                       i++;
+                                       continue;
+                               }
+                               usage(blame_usage);
+                       } else if(!strcmp(argv[i], "--")) {
+                               options = 0;
+                               continue;
+                       } else if(argv[i][0] == '-')
+                               usage(blame_usage);
+                       else
+                               options = 0;
+               }
+
+               if(!options) {
+                       if(!filename)
+                               filename = argv[i];
+                       else if(!commit)
+                               commit = argv[i];
+                       else
+                               usage(blame_usage);
+               }
+       }
+
+       if(!filename)
+               usage(blame_usage);
+       if (commit && sha1_p)
+               usage(blame_usage);
+       else if(!commit)
+               commit = "HEAD";
+
+       if(prefix)
+               sprintf(filename_buf, "%s%s", prefix, filename);
+       else
+               strcpy(filename_buf, filename);
+       filename = filename_buf;
+
+       if (!sha1_p) {
+               if (get_sha1(commit, sha1))
+                       die("get_sha1 failed, commit '%s' not found", commit);
+               sha1_p = sha1;
+       }
+       start_commit = lookup_commit_reference(sha1_p);
+       get_util(start_commit)->pathname = filename;
+       if (fill_util_info(start_commit)) {
+               printf("%s not found in %s\n", filename, commit);
+               return 1;
+       }
+
+
+       init_revisions(&rev);
+       rev.remove_empty_trees = 1;
+       rev.topo_order = 1;
+       rev.prune_fn = simplify_commit;
+       rev.topo_setter = topo_setter;
+       rev.topo_getter = topo_getter;
+       rev.parents = 1;
+       rev.limited = 1;
+
+       commit_list_insert(start_commit, &rev.commits);
+
+       args[0] = filename;
+       args[1] = NULL;
+       diff_tree_setup_paths(args, &rev.pruning);
+       prepare_revision_walk(&rev);
+       process_commits(&rev, filename, &initial);
+
+       buf = blame_contents;
+       for (max_digits = 1, i = 10; i <= num_blame_lines + 1; max_digits++)
+               i *= 10;
+
+       longest_file = 0;
+       longest_author = 0;
+       found_rename = 0;
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               struct util_info* u;
+               if (!c)
+                       c = initial;
+               u = c->object.util;
+
+               if (!found_rename && strcmp(filename, u->pathname))
+                       found_rename = 1;
+               if (longest_file < strlen(u->pathname))
+                       longest_file = strlen(u->pathname);
+               get_commit_info(c, &ci);
+               if (longest_author < strlen(ci.author))
+                       longest_author = strlen(ci.author);
+       }
+
+       for (i = 0; i < num_blame_lines; i++) {
+               struct commit *c = blame_lines[i];
+               struct util_info* u;
+
+               if (!c)
+                       c = initial;
+
+               u = c->object.util;
+               get_commit_info(c, &ci);
+               fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout);
+               if(compability) {
+                       printf("\t(%10s\t%10s\t%d)", ci.author,
+                              format_time(ci.author_time, ci.author_tz), i+1);
+               } else {
+                       if (found_rename)
+                               printf(" %-*.*s", longest_file, longest_file,
+                                      u->pathname);
+                       printf(" (%-*.*s %10s %*d) ",
+                              longest_author, longest_author, ci.author,
+                              format_time(ci.author_time, ci.author_tz),
+                              max_digits, i+1);
+               }
+
+               if(i == num_blame_lines - 1) {
+                       fwrite(buf, blame_len - (buf - blame_contents),
+                              1, stdout);
+                       if(blame_contents[blame_len-1] != '\n')
+                               putc('\n', stdout);
+               } else {
+                       char* next_buf = strchr(buf, '\n') + 1;
+                       fwrite(buf, next_buf - buf, 1, stdout);
+                       buf = next_buf;
+               }
+       }
+
+       if (DEBUG) {
+               printf("num get patch: %d\n", num_get_patch);
+               printf("num commits: %d\n", num_commits);
+               printf("patch time: %f\n", patch_time / 1000000.0);
+               printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
+       }
+
+       return 0;
+}
diff --git a/blob.c b/blob.c
new file mode 100644 (file)
index 0000000..c1fdd86
--- /dev/null
+++ b/blob.c
@@ -0,0 +1,51 @@
+#include "cache.h"
+#include "blob.h"
+#include <stdlib.h>
+
+const char *blob_type = "blob";
+
+struct blob *lookup_blob(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct blob *ret = xcalloc(1, sizeof(struct blob));
+               created_object(sha1, &ret->object);
+               ret->object.type = blob_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = blob_type;
+       if (obj->type != blob_type) {
+               error("Object %s is a %s, not a blob", 
+                     sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct blob *) obj;
+}
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
+{
+       item->object.parsed = 1;
+       return 0;
+}
+
+int parse_blob(struct blob *item)
+{
+        char type[20];
+        void *buffer;
+        unsigned long size;
+       int ret;
+
+        if (item->object.parsed)
+                return 0;
+        buffer = read_sha1_file(item->object.sha1, type, &size);
+        if (!buffer)
+                return error("Could not read %s",
+                             sha1_to_hex(item->object.sha1));
+        if (strcmp(type, blob_type))
+                return error("Object %s not a blob",
+                             sha1_to_hex(item->object.sha1));
+       ret = parse_blob_buffer(item, buffer, size);
+       free(buffer);
+       return ret;
+}
diff --git a/blob.h b/blob.h
new file mode 100644 (file)
index 0000000..ea5d9e9
--- /dev/null
+++ b/blob.h
@@ -0,0 +1,18 @@
+#ifndef BLOB_H
+#define BLOB_H
+
+#include "object.h"
+
+extern const char *blob_type;
+
+struct blob {
+       struct object object;
+};
+
+struct blob *lookup_blob(const unsigned char *sha1);
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size);
+
+int parse_blob(struct blob *item);
+
+#endif /* BLOB_H */
diff --git a/builtin-add.c b/builtin-add.c
new file mode 100644 (file)
index 0000000..bfbbb1b
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+
+static const char builtin_add_usage[] =
+"git-add [-n] [-v] <filepattern>...";
+
+static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+       char *seen;
+       int i, specs;
+       struct dir_entry **src, **dst;
+
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xmalloc(specs);
+       memset(seen, 0, specs);
+
+       src = dst = dir->entries;
+       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;
+       }
+       dir->nr = dst - dir->entries;
+
+       for (i = 0; i < specs; i++) {
+               struct stat st;
+               const char *match;
+               if (seen[i])
+                       continue;
+
+               /* Existing file? We must have ignored it */
+               match = pathspec[i];
+               if (!match[0] || !lstat(match, &st))
+                       continue;
+               die("pathspec '%s' did not match any files", match);
+       }
+}
+
+static void fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+       const char *path, *base;
+       int baselen;
+
+       /* Set up the default git porcelain excludes */
+       memset(dir, 0, sizeof(*dir));
+       dir->exclude_per_dir = ".gitignore";
+       path = git_path("info/exclude");
+       if (!access(path, R_OK))
+               add_excludes_from_file(dir, path);
+
+       /*
+        * Calculate common prefix for the pathspec, and
+        * use that to optimize the directory walk
+        */
+       baselen = common_prefix(pathspec);
+       path = ".";
+       base = "";
+       if (baselen) {
+               char *common = xmalloc(baselen + 1);
+               common = xmalloc(baselen + 1);
+               memcpy(common, *pathspec, baselen);
+               common[baselen] = 0;
+               path = base = common;
+       }
+
+       /* Read the directory and prune it */
+       read_directory(dir, path, base, baselen);
+       if (pathspec)
+               prune_directory(dir, pathspec, baselen);
+}
+
+static int add_file_to_index(const char *path, int verbose)
+{
+       int size, namelen;
+       struct stat st;
+       struct cache_entry *ce;
+
+       if (lstat(path, &st))
+               die("%s: unable to stat (%s)", path, strerror(errno));
+
+       if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+               die("%s: can only add regular files or symbolic links", path);
+
+       namelen = strlen(path);
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
+       fill_stat_cache_info(ce, &st);
+
+       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.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (pos >= 0)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
+       }
+
+       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))
+               die("unable to add %s to index",path);
+       if (verbose)
+               printf("add '%s'\n", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
+       return 0;
+}
+
+static struct lock_file lock_file;
+
+int cmd_add(int argc, const char **argv, char **envp)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       struct dir_struct dir;
+
+       git_config(git_default_config);
+
+       newfd = hold_lock_file_for_update(&lock_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               die(builtin_add_usage);
+       }
+       git_config(git_default_config);
+       pathspec = get_pathspec(prefix, argv + i);
+
+       fill_directory(&dir, pathspec);
+
+       if (show_only) {
+               const char *sep = "", *eof = "";
+               for (i = 0; i < dir.nr; i++) {
+                       printf("%s%s", sep, dir.entries[i]->name);
+                       sep = " ";
+                       eof = "\n";
+               }
+               fputs(eof, stdout);
+               return 0;
+       }
+
+       for (i = 0; i < dir.nr; i++)
+               add_file_to_index(dir.entries[i]->name, verbose);
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_lock_file(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin-apply.c b/builtin-apply.c
new file mode 100644 (file)
index 0000000..e113c74
--- /dev/null
@@ -0,0 +1,2324 @@
+/*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * 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"
+#include "blob.h"
+#include "delta.h"
+#include "builtin.h"
+
+//  --check turns on checking that the working tree matches the
+//    files that are being modified, but doesn't apply the patch
+//  --stat does just a diffstat, and doesn't actually apply
+//  --numstat does numeric diffstat, and doesn't actually apply
+//  --index-info shows the old and new index info for paths if available.
+//  --index updates the cache as well.
+//  --cached updates only the cache without ever touching the working tree.
+//
+static const char *prefix;
+static int prefix_length = -1;
+static int newfd = -1;
+
+static int p_value = 1;
+static int allow_binary_replacement = 0;
+static int check_index = 0;
+static int write_index = 0;
+static int cached = 0;
+static int diffstat = 0;
+static int numstat = 0;
+static int summary = 0;
+static int check = 0;
+static int apply = 1;
+static int no_add = 0;
+static int show_index_info = 0;
+static int line_termination = '\n';
+static unsigned long p_context = -1;
+static const char apply_usage[] =
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+
+static enum whitespace_eol {
+       nowarn_whitespace,
+       warn_on_whitespace,
+       error_on_whitespace,
+       strip_whitespace,
+} new_whitespace = warn_on_whitespace;
+static int whitespace_error = 0;
+static int squelch_whitespace_errors = 5;
+static int applied_after_stripping = 0;
+static const char *patch_input_file = NULL;
+
+static void parse_whitespace_option(const char *option)
+{
+       if (!option) {
+               new_whitespace = warn_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "warn")) {
+               new_whitespace = warn_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "nowarn")) {
+               new_whitespace = nowarn_whitespace;
+               return;
+       }
+       if (!strcmp(option, "error")) {
+               new_whitespace = error_on_whitespace;
+               return;
+       }
+       if (!strcmp(option, "error-all")) {
+               new_whitespace = error_on_whitespace;
+               squelch_whitespace_errors = 0;
+               return;
+       }
+       if (!strcmp(option, "strip")) {
+               new_whitespace = strip_whitespace;
+               return;
+       }
+       die("unrecognized whitespace option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+       if (!whitespace_option && !apply_default_whitespace) {
+               new_whitespace = (apply
+                                 ? warn_on_whitespace
+                                 : nowarn_whitespace);
+       }
+}
+
+/*
+ * For "diff-stat" like behaviour, we keep track of the biggest change
+ * we've seen, and the longest filename. That allows us to do simple
+ * scaling.
+ */
+static int max_change, max_len;
+
+/*
+ * Various "current state", notably line numbers and what
+ * file (and how) we're patching right now.. The "is_xxxx"
+ * things are flags, where -1 means "don't know yet".
+ */
+static int linenr = 1;
+
+struct fragment {
+       unsigned long leading, trailing;
+       unsigned long oldpos, oldlines;
+       unsigned long newpos, newlines;
+       const char *patch;
+       int size;
+       struct fragment *next;
+};
+
+struct patch {
+       char *new_name, *old_name, *def_name;
+       unsigned int old_mode, new_mode;
+       int is_rename, is_copy, is_new, is_delete, is_binary;
+#define BINARY_DELTA_DEFLATED 1
+#define BINARY_LITERAL_DEFLATED 2
+       unsigned long deflate_origlen;
+       int lines_added, lines_deleted;
+       int score;
+       struct fragment *fragments;
+       char *result;
+       unsigned long resultsize;
+       char old_sha1_prefix[41];
+       char new_sha1_prefix[41];
+       struct patch *next;
+};
+
+#define CHUNKSIZE (8192)
+#define SLOP (16)
+
+static void *read_patch_file(int fd, unsigned long *sizep)
+{
+       unsigned long size = 0, alloc = CHUNKSIZE;
+       void *buffer = xmalloc(alloc);
+
+       for (;;) {
+               int nr = alloc - size;
+               if (nr < 1024) {
+                       alloc += CHUNKSIZE;
+                       buffer = xrealloc(buffer, alloc);
+                       nr = alloc - size;
+               }
+               nr = xread(fd, buffer + size, nr);
+               if (!nr)
+                       break;
+               if (nr < 0)
+                       die("git-apply: read returned %s", strerror(errno));
+               size += nr;
+       }
+       *sizep = size;
+
+       /*
+        * Make sure that we have some slop in the buffer
+        * so that we can do speculative "memcmp" etc, and
+        * see to it that it is NUL-filled.
+        */
+       if (alloc < size + SLOP)
+               buffer = xrealloc(buffer, size + SLOP);
+       memset(buffer + size, 0, SLOP);
+       return buffer;
+}
+
+static unsigned long linelen(const char *buffer, unsigned long size)
+{
+       unsigned long len = 0;
+       while (size--) {
+               len++;
+               if (*buffer++ == '\n')
+                       break;
+       }
+       return len;
+}
+
+static int is_dev_null(const char *str)
+{
+       return !memcmp("/dev/null", str, 9) && isspace(str[9]);
+}
+
+#define TERM_SPACE     1
+#define TERM_TAB       2
+
+static int name_terminate(const char *name, int namelen, int c, int terminate)
+{
+       if (c == ' ' && !(terminate & TERM_SPACE))
+               return 0;
+       if (c == '\t' && !(terminate & TERM_TAB))
+               return 0;
+
+       return 1;
+}
+
+static char * find_name(const char *line, char *def, int p_value, int terminate)
+{
+       int len;
+       const char *start = line;
+       char *name;
+
+       if (*line == '"') {
+               /* Proposed "new-style" GNU patch/diff format; see
+                * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+                */
+               name = unquote_c_style(line, NULL);
+               if (name) {
+                       char *cp = name;
+                       while (p_value) {
+                               cp = strchr(name, '/');
+                               if (!cp)
+                                       break;
+                               cp++;
+                               p_value--;
+                       }
+                       if (cp) {
+                               /* name can later be freed, so we need
+                                * to memmove, not just return cp
+                                */
+                               memmove(name, cp, strlen(cp) + 1);
+                               free(def);
+                               return name;
+                       }
+                       else {
+                               free(name);
+                               name = NULL;
+                       }
+               }
+       }
+
+       for (;;) {
+               char c = *line;
+
+               if (isspace(c)) {
+                       if (c == '\n')
+                               break;
+                       if (name_terminate(start, line-start, c, terminate))
+                               break;
+               }
+               line++;
+               if (c == '/' && !--p_value)
+                       start = line;
+       }
+       if (!start)
+               return def;
+       len = line - start;
+       if (!len)
+               return def;
+
+       /*
+        * Generally we prefer the shorter name, especially
+        * if the other one is just a variation of that with
+        * something else tacked on to the end (ie "file.orig"
+        * or "file~").
+        */
+       if (def) {
+               int deflen = strlen(def);
+               if (deflen < len && !strncmp(start, def, deflen))
+                       return def;
+       }
+
+       name = xmalloc(len + 1);
+       memcpy(name, start, len);
+       name[len] = 0;
+       free(def);
+       return name;
+}
+
+/*
+ * Get the name etc info from the --/+++ lines of a traditional patch header
+ *
+ * NOTE! This hardcodes "-p1" behaviour in filename detection.
+ *
+ * FIXME! The end-of-filename heuristics are kind of screwy. For existing
+ * files, we can happily check the index for a match, but for creating a
+ * new file we should try to match whatever "patch" does. I have no idea.
+ */
+static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
+{
+       char *name;
+
+       first += 4;     // skip "--- "
+       second += 4;    // skip "+++ "
+       if (is_dev_null(first)) {
+               patch->is_new = 1;
+               patch->is_delete = 0;
+               name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
+               patch->new_name = name;
+       } else if (is_dev_null(second)) {
+               patch->is_new = 0;
+               patch->is_delete = 1;
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+               patch->old_name = name;
+       } else {
+               name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+               name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
+               patch->old_name = patch->new_name = name;
+       }
+       if (!name)
+               die("unable to find filename in patch at line %d", linenr);
+}
+
+static int gitdiff_hdrend(const char *line, struct patch *patch)
+{
+       return -1;
+}
+
+/*
+ * We're anal about diff header consistency, to make
+ * sure that we don't end up having strange ambiguous
+ * patches floating around.
+ *
+ * As a result, gitdiff_{old|new}name() will check
+ * their names against any previous information, just
+ * to make sure..
+ */
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+{
+       if (!orig_name && !isnull)
+               return find_name(line, NULL, 1, 0);
+
+       if (orig_name) {
+               int len;
+               const char *name;
+               char *another;
+               name = orig_name;
+               len = strlen(name);
+               if (isnull)
+                       die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+               another = find_name(line, NULL, 1, 0);
+               if (!another || memcmp(another, name, len))
+                       die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+               free(another);
+               return orig_name;
+       }
+       else {
+               /* expect "/dev/null" */
+               if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+                       die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
+               return NULL;
+       }
+}
+
+static int gitdiff_oldname(const char *line, struct patch *patch)
+{
+       patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+       return 0;
+}
+
+static int gitdiff_newname(const char *line, struct patch *patch)
+{
+       patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+       return 0;
+}
+
+static int gitdiff_oldmode(const char *line, struct patch *patch)
+{
+       patch->old_mode = strtoul(line, NULL, 8);
+       return 0;
+}
+
+static int gitdiff_newmode(const char *line, struct patch *patch)
+{
+       patch->new_mode = strtoul(line, NULL, 8);
+       return 0;
+}
+
+static int gitdiff_delete(const char *line, struct patch *patch)
+{
+       patch->is_delete = 1;
+       patch->old_name = patch->def_name;
+       return gitdiff_oldmode(line, patch);
+}
+
+static int gitdiff_newfile(const char *line, struct patch *patch)
+{
+       patch->is_new = 1;
+       patch->new_name = patch->def_name;
+       return gitdiff_newmode(line, patch);
+}
+
+static int gitdiff_copysrc(const char *line, struct patch *patch)
+{
+       patch->is_copy = 1;
+       patch->old_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_copydst(const char *line, struct patch *patch)
+{
+       patch->is_copy = 1;
+       patch->new_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_renamesrc(const char *line, struct patch *patch)
+{
+       patch->is_rename = 1;
+       patch->old_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_renamedst(const char *line, struct patch *patch)
+{
+       patch->is_rename = 1;
+       patch->new_name = find_name(line, NULL, 0, 0);
+       return 0;
+}
+
+static int gitdiff_similarity(const char *line, struct patch *patch)
+{
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
+       return 0;
+}
+
+static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+{
+       if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+               patch->score = 0;
+       return 0;
+}
+
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+       /* index line is N hexadecimal, "..", N hexadecimal,
+        * and optional space with octal mode.
+        */
+       const char *ptr, *eol;
+       int len;
+
+       ptr = strchr(line, '.');
+       if (!ptr || ptr[1] != '.' || 40 < ptr - line)
+               return 0;
+       len = ptr - line;
+       memcpy(patch->old_sha1_prefix, line, len);
+       patch->old_sha1_prefix[len] = 0;
+
+       line = ptr + 2;
+       ptr = strchr(line, ' ');
+       eol = strchr(line, '\n');
+
+       if (!ptr || eol < ptr)
+               ptr = eol;
+       len = ptr - line;
+
+       if (40 < len)
+               return 0;
+       memcpy(patch->new_sha1_prefix, line, len);
+       patch->new_sha1_prefix[len] = 0;
+       if (*ptr == ' ')
+               patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8);
+       return 0;
+}
+
+/*
+ * This is normal for a diff that doesn't change anything: we'll fall through
+ * into the next diff. Tell the parser to break out.
+ */
+static int gitdiff_unrecognized(const char *line, struct patch *patch)
+{
+       return -1;
+}
+
+static const char *stop_at_slash(const char *line, int llen)
+{
+       int i;
+
+       for (i = 0; i < llen; i++) {
+               int ch = line[i];
+               if (ch == '/')
+                       return line + i;
+       }
+       return NULL;
+}
+
+/* This is to extract the same name that appears on "diff --git"
+ * line.  We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file.  In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
+{
+       int len;
+       const char *name;
+       const char *second = NULL;
+
+       line += strlen("diff --git ");
+       llen -= strlen("diff --git ");
+
+       if (*line == '"') {
+               const char *cp;
+               char *first = unquote_c_style(line, &second);
+               if (!first)
+                       return NULL;
+
+               /* advance to the first slash */
+               cp = stop_at_slash(first, strlen(first));
+               if (!cp || cp == first) {
+                       /* we do not accept absolute paths */
+               free_first_and_fail:
+                       free(first);
+                       return NULL;
+               }
+               len = strlen(cp+1);
+               memmove(first, cp+1, len+1); /* including NUL */
+
+               /* second points at one past closing dq of name.
+                * find the second name.
+                */
+               while ((second < line + llen) && isspace(*second))
+                       second++;
+
+               if (line + llen <= second)
+                       goto free_first_and_fail;
+               if (*second == '"') {
+                       char *sp = unquote_c_style(second, NULL);
+                       if (!sp)
+                               goto free_first_and_fail;
+                       cp = stop_at_slash(sp, strlen(sp));
+                       if (!cp || cp == sp) {
+                       free_both_and_fail:
+                               free(sp);
+                               goto free_first_and_fail;
+                       }
+                       /* They must match, otherwise ignore */
+                       if (strcmp(cp+1, first))
+                               goto free_both_and_fail;
+                       free(sp);
+                       return first;
+               }
+
+               /* unquoted second */
+               cp = stop_at_slash(second, line + llen - second);
+               if (!cp || cp == second)
+                       goto free_first_and_fail;
+               cp++;
+               if (line + llen - cp != len + 1 ||
+                   memcmp(first, cp, len))
+                       goto free_first_and_fail;
+               return first;
+       }
+
+       /* unquoted first name */
+       name = stop_at_slash(line, llen);
+       if (!name || name == line)
+               return NULL;
+
+       name++;
+
+       /* since the first name is unquoted, a dq if exists must be
+        * the beginning of the second name.
+        */
+       for (second = name; second < line + llen; second++) {
+               if (*second == '"') {
+                       const char *cp = second;
+                       const char *np;
+                       char *sp = unquote_c_style(second, NULL);
+
+                       if (!sp)
+                               return NULL;
+                       np = stop_at_slash(sp, strlen(sp));
+                       if (!np || np == sp) {
+                       free_second_and_fail:
+                               free(sp);
+                               return NULL;
+                       }
+                       np++;
+                       len = strlen(np);
+                       if (len < cp - name &&
+                           !strncmp(np, name, len) &&
+                           isspace(name[len])) {
+                               /* Good */
+                               memmove(sp, np, len + 1);
+                               return sp;
+                       }
+                       goto free_second_and_fail;
+               }
+       }
+
+       /*
+        * Accept a name only if it shows up twice, exactly the same
+        * form.
+        */
+       for (len = 0 ; ; len++) {
+               char c = name[len];
+
+               switch (c) {
+               default:
+                       continue;
+               case '\n':
+                       return NULL;
+               case '\t': case ' ':
+                       second = name+len;
+                       for (;;) {
+                               char c = *second++;
+                               if (c == '\n')
+                                       return NULL;
+                               if (c == '/')
+                                       break;
+                       }
+                       if (second[len] == '\n' && !memcmp(name, second, len)) {
+                               char *ret = xmalloc(len + 1);
+                               memcpy(ret, name, len);
+                               ret[len] = 0;
+                               return ret;
+                       }
+               }
+       }
+       return NULL;
+}
+
+/* Verify that we recognize the lines following a git header */
+static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+{
+       unsigned long offset;
+
+       /* A git diff has explicit new/delete information, so we don't guess */
+       patch->is_new = 0;
+       patch->is_delete = 0;
+
+       /*
+        * Some things may not have the old name in the
+        * rest of the headers anywhere (pure mode changes,
+        * or removing or adding empty files), so we get
+        * the default name from the header.
+        */
+       patch->def_name = git_header_name(line, len);
+
+       line += len;
+       size -= len;
+       linenr++;
+       for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
+               static const struct opentry {
+                       const char *str;
+                       int (*fn)(const char *, struct patch *);
+               } optable[] = {
+                       { "@@ -", gitdiff_hdrend },
+                       { "--- ", gitdiff_oldname },
+                       { "+++ ", gitdiff_newname },
+                       { "old mode ", gitdiff_oldmode },
+                       { "new mode ", gitdiff_newmode },
+                       { "deleted file mode ", gitdiff_delete },
+                       { "new file mode ", gitdiff_newfile },
+                       { "copy from ", gitdiff_copysrc },
+                       { "copy to ", gitdiff_copydst },
+                       { "rename old ", gitdiff_renamesrc },
+                       { "rename new ", gitdiff_renamedst },
+                       { "rename from ", gitdiff_renamesrc },
+                       { "rename to ", gitdiff_renamedst },
+                       { "similarity index ", gitdiff_similarity },
+                       { "dissimilarity index ", gitdiff_dissimilarity },
+                       { "index ", gitdiff_index },
+                       { "", gitdiff_unrecognized },
+               };
+               int i;
+
+               len = linelen(line, size);
+               if (!len || line[len-1] != '\n')
+                       break;
+               for (i = 0; i < ARRAY_SIZE(optable); i++) {
+                       const struct opentry *p = optable + i;
+                       int oplen = strlen(p->str);
+                       if (len < oplen || memcmp(p->str, line, oplen))
+                               continue;
+                       if (p->fn(line + oplen, patch) < 0)
+                               return offset;
+                       break;
+               }
+       }
+
+       return offset;
+}
+
+static int parse_num(const char *line, unsigned long *p)
+{
+       char *ptr;
+
+       if (!isdigit(*line))
+               return 0;
+       *p = strtoul(line, &ptr, 10);
+       return ptr - line;
+}
+
+static int parse_range(const char *line, int len, int offset, const char *expect,
+                       unsigned long *p1, unsigned long *p2)
+{
+       int digits, ex;
+
+       if (offset < 0 || offset >= len)
+               return -1;
+       line += offset;
+       len -= offset;
+
+       digits = parse_num(line, p1);
+       if (!digits)
+               return -1;
+
+       offset += digits;
+       line += digits;
+       len -= digits;
+
+       *p2 = 1;
+       if (*line == ',') {
+               digits = parse_num(line+1, p2);
+               if (!digits)
+                       return -1;
+
+               offset += digits+1;
+               line += digits+1;
+               len -= digits+1;
+       }
+
+       ex = strlen(expect);
+       if (ex > len)
+               return -1;
+       if (memcmp(line, expect, ex))
+               return -1;
+
+       return offset + ex;
+}
+
+/*
+ * Parse a unified diff fragment header of the
+ * form "@@ -a,b +c,d @@"
+ */
+static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+{
+       int offset;
+
+       if (!len || line[len-1] != '\n')
+               return -1;
+
+       /* Figure out the number of lines in a fragment */
+       offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
+       offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
+
+       return offset;
+}
+
+static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+{
+       unsigned long offset, len;
+
+       patch->is_rename = patch->is_copy = 0;
+       patch->is_new = patch->is_delete = -1;
+       patch->old_mode = patch->new_mode = 0;
+       patch->old_name = patch->new_name = NULL;
+       for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
+               unsigned long nextlen;
+
+               len = linelen(line, size);
+               if (!len)
+                       break;
+
+               /* Testing this early allows us to take a few shortcuts.. */
+               if (len < 6)
+                       continue;
+
+               /*
+                * Make sure we don't find any unconnected patch fragmants.
+                * That's a sign that we didn't find a header, and that a
+                * patch has become corrupted/broken up.
+                */
+               if (!memcmp("@@ -", line, 4)) {
+                       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);
+               }
+
+               if (size < len + 6)
+                       break;
+
+               /*
+                * Git patch? It might not have a real patch, just a rename
+                * or mode change, so we handle that specially
+                */
+               if (!memcmp("diff --git ", line, 11)) {
+                       int git_hdr_len = parse_git_header(line, len, size, patch);
+                       if (git_hdr_len <= len)
+                               continue;
+                       if (!patch->old_name && !patch->new_name) {
+                               if (!patch->def_name)
+                                       die("git diff header lacks filename information (line %d)", linenr);
+                               patch->old_name = patch->new_name = patch->def_name;
+                       }
+                       *hdrsize = git_hdr_len;
+                       return offset;
+               }
+
+               /** --- followed by +++ ? */
+               if (memcmp("--- ", line,  4) || memcmp("+++ ", line + len, 4))
+                       continue;
+
+               /*
+                * We only accept unified patches, so we want it to
+                * at least have "@@ -a,b +c,d @@\n", which is 14 chars
+                * minimum
+                */
+               nextlen = linelen(line + len, size - len);
+               if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
+                       continue;
+
+               /* Ok, we'll consider it a patch */
+               parse_traditional_patch(line, line+len, patch);
+               *hdrsize = len + nextlen;
+               linenr += 2;
+               return offset;
+       }
+       return -1;
+}
+
+/*
+ * Parse a unified diff. Note that this really needs
+ * to parse each fragment separately, since the only
+ * way to know the difference between a "---" that is
+ * part of a patch, and a "---" that starts the next
+ * patch is to look at the line counts..
+ */
+static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
+{
+       int added, deleted;
+       int len = linelen(line, size), offset;
+       unsigned long oldlines, newlines;
+       unsigned long leading, trailing;
+
+       offset = parse_fragment_header(line, len, fragment);
+       if (offset < 0)
+               return -1;
+       oldlines = fragment->oldlines;
+       newlines = fragment->newlines;
+       leading = 0;
+       trailing = 0;
+
+       if (patch->is_new < 0) {
+               patch->is_new =  !oldlines;
+               if (!oldlines)
+                       patch->old_name = NULL;
+       }
+       if (patch->is_delete < 0) {
+               patch->is_delete = !newlines;
+               if (!newlines)
+                       patch->new_name = NULL;
+       }
+
+       if (patch->is_new && oldlines)
+               return error("new file depends on old contents");
+       if (patch->is_delete != !newlines) {
+               if (newlines)
+                       return error("deleted file still has contents");
+               fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
+       }
+
+       /* Parse the thing.. */
+       line += len;
+       size -= len;
+       linenr++;
+       added = deleted = 0;
+       for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+               if (!oldlines && !newlines)
+                       break;
+               len = linelen(line, size);
+               if (!len || line[len-1] != '\n')
+                       return -1;
+               switch (*line) {
+               default:
+                       return -1;
+               case ' ':
+                       oldlines--;
+                       newlines--;
+                       if (!deleted && !added)
+                               leading++;
+                       trailing++;
+                       break;
+               case '-':
+                       deleted++;
+                       oldlines--;
+                       trailing = 0;
+                       break;
+               case '+':
+                       /*
+                        * We know len is at least two, since we have a '+' and
+                        * we checked that the last character was a '\n' above.
+                        * That is, an addition of an empty line would check
+                        * the '+' here.  Sneaky...
+                        */
+                       if ((new_whitespace != nowarn_whitespace) &&
+                           isspace(line[len-2])) {
+                               whitespace_error++;
+                               if (squelch_whitespace_errors &&
+                                   squelch_whitespace_errors <
+                                   whitespace_error)
+                                       ;
+                               else {
+                                       fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
+                                               patch_input_file,
+                                               linenr, len-2, line+1);
+                               }
+                       }
+                       added++;
+                       newlines--;
+                       trailing = 0;
+                       break;
+
+                /* We allow "\ No newline at end of file". Depending
+                 * on locale settings when the patch was produced we
+                 * don't know what this line looks like. The only
+                 * thing we do know is that it begins with "\ ".
+                * Checking for 12 is just for sanity check -- any
+                * l10n of "\ No newline..." is at least that long.
+                */
+               case '\\':
+                       if (len < 12 || memcmp(line, "\\ ", 2))
+                               return -1;
+                       break;
+               }
+       }
+       if (oldlines || newlines)
+               return -1;
+       fragment->leading = leading;
+       fragment->trailing = trailing;
+
+       /* If a fragment ends with an incomplete line, we failed to include
+        * it in the above loop because we hit oldlines == newlines == 0
+        * before seeing it.
+        */
+       if (12 < size && !memcmp(line, "\\ ", 2))
+               offset += linelen(line, size);
+
+       patch->lines_added += added;
+       patch->lines_deleted += deleted;
+       return offset;
+}
+
+static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+{
+       unsigned long offset = 0;
+       struct fragment **fragp = &patch->fragments;
+
+       while (size > 4 && !memcmp(line, "@@ -", 4)) {
+               struct fragment *fragment;
+               int len;
+
+               fragment = xcalloc(1, sizeof(*fragment));
+               len = parse_fragment(line, size, patch, fragment);
+               if (len <= 0)
+                       die("corrupt patch at line %d", linenr);
+
+               fragment->patch = line;
+               fragment->size = len;
+
+               *fragp = fragment;
+               fragp = &fragment->next;
+
+               offset += len;
+               line += len;
+               size -= len;
+       }
+       return offset;
+}
+
+static inline int metadata_changes(struct patch *patch)
+{
+       return  patch->is_rename > 0 ||
+               patch->is_copy > 0 ||
+               patch->is_new > 0 ||
+               patch->is_delete ||
+               (patch->old_mode && patch->new_mode &&
+                patch->old_mode != patch->new_mode);
+}
+
+static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+{
+       /* We have read "GIT binary patch\n"; what follows is a line
+        * that says the patch method (currently, either "deflated
+        * literal" or "deflated delta") and the length of data before
+        * deflating; a sequence of 'length-byte' followed by base-85
+        * encoded data follows.
+        *
+        * Each 5-byte sequence of base-85 encodes up to 4 bytes,
+        * and we would limit the patch line to 66 characters,
+        * so one line can fit up to 13 groups that would decode
+        * to 52 bytes max.  The length byte 'A'-'Z' corresponds
+        * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
+        * The end of binary is signalled with an empty line.
+        */
+       int llen, used;
+       struct fragment *fragment;
+       char *data = NULL;
+
+       patch->fragments = fragment = xcalloc(1, sizeof(*fragment));
+
+       /* Grab the type of patch */
+       llen = linelen(buffer, size);
+       used = llen;
+       linenr++;
+
+       if (!strncmp(buffer, "delta ", 6)) {
+               patch->is_binary = BINARY_DELTA_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 6, NULL, 10);
+       }
+       else if (!strncmp(buffer, "literal ", 8)) {
+               patch->is_binary = BINARY_LITERAL_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 8, NULL, 10);
+       }
+       else
+               return error("unrecognized binary patch at line %d: %.*s",
+                            linenr-1, llen-1, buffer);
+       buffer += llen;
+       while (1) {
+               int byte_length, max_byte_length, newsize;
+               llen = linelen(buffer, size);
+               used += llen;
+               linenr++;
+               if (llen == 1)
+                       break;
+               /* Minimum line is "A00000\n" which is 7-byte long,
+                * and the line length must be multiple of 5 plus 2.
+                */
+               if ((llen < 7) || (llen-2) % 5)
+                       goto corrupt;
+               max_byte_length = (llen - 2) / 5 * 4;
+               byte_length = *buffer;
+               if ('A' <= byte_length && byte_length <= 'Z')
+                       byte_length = byte_length - 'A' + 1;
+               else if ('a' <= byte_length && byte_length <= 'z')
+                       byte_length = byte_length - 'a' + 27;
+               else
+                       goto corrupt;
+               /* if the input length was not multiple of 4, we would
+                * have filler at the end but the filler should never
+                * exceed 3 bytes
+                */
+               if (max_byte_length < byte_length ||
+                   byte_length <= max_byte_length - 4)
+                       goto corrupt;
+               newsize = fragment->size + byte_length;
+               data = xrealloc(data, newsize);
+               if (decode_85(data + fragment->size,
+                             buffer + 1,
+                             byte_length))
+                       goto corrupt;
+               fragment->size = newsize;
+               buffer += llen;
+               size -= llen;
+       }
+       fragment->patch = data;
+       return used;
+ corrupt:
+       return error("corrupt binary patch at line %d: %.*s",
+                    linenr-1, llen-1, buffer);
+}
+
+static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
+{
+       int hdrsize, patchsize;
+       int offset = find_header(buffer, size, &hdrsize, patch);
+
+       if (offset < 0)
+               return offset;
+
+       patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
+
+       if (!patchsize) {
+               static const char *binhdr[] = {
+                       "Binary files ",
+                       "Files ",
+                       NULL,
+               };
+               static const char git_binary[] = "GIT binary patch\n";
+               int i;
+               int hd = hdrsize + offset;
+               unsigned long llen = linelen(buffer + hd, size - hd);
+
+               if (llen == sizeof(git_binary) - 1 &&
+                   !memcmp(git_binary, buffer + hd, llen)) {
+                       int used;
+                       linenr++;
+                       used = parse_binary(buffer + hd + llen,
+                                           size - hd - llen, patch);
+                       if (used)
+                               patchsize = used + llen;
+                       else
+                               patchsize = 0;
+               }
+               else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
+                       for (i = 0; binhdr[i]; i++) {
+                               int len = strlen(binhdr[i]);
+                               if (len < size - hd &&
+                                   !memcmp(binhdr[i], buffer + hd, len)) {
+                                       linenr++;
+                                       patch->is_binary = 1;
+                                       patchsize = llen;
+                                       break;
+                               }
+                       }
+               }
+
+               /* Empty patch cannot be applied if:
+                * - it is a binary patch and we do not do binary_replace, or
+                * - text patch without metadata change
+                */
+               if ((apply || check) &&
+                   (patch->is_binary
+                    ? !allow_binary_replacement
+                    : !metadata_changes(patch)))
+                       die("patch with only garbage at line %d", linenr);
+       }
+
+       return offset + hdrsize + patchsize;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+       const char *prefix = "";
+       char *name = patch->new_name;
+       char *qname = NULL;
+       int len, max, add, del, total;
+
+       if (!name)
+               name = patch->old_name;
+
+       if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+               qname = xmalloc(len + 1);
+               quote_c_style(name, qname, NULL, 0);
+               name = qname;
+       }
+
+       /*
+        * "scale" the filename
+        */
+       len = strlen(name);
+       max = max_len;
+       if (max > 50)
+               max = 50;
+       if (len > max) {
+               char *slash;
+               prefix = "...";
+               max -= 3;
+               name += len - max;
+               slash = strchr(name, '/');
+               if (slash)
+                       name = slash;
+       }
+       len = max;
+
+       /*
+        * scale the add/delete
+        */
+       max = max_change;
+       if (max + len > 70)
+               max = 70 - len;
+
+       add = patch->lines_added;
+       del = patch->lines_deleted;
+       total = add + del;
+
+       if (max_change > 0) {
+               total = (total * max + max_change / 2) / max_change;
+               add = (add * max + max_change / 2) / max_change;
+               del = total - add;
+       }
+       if (patch->is_binary)
+               printf(" %s%-*s |  Bin\n", prefix, len, name);
+       else
+               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+                      len, name, patch->lines_added + patch->lines_deleted,
+                      add, pluses, del, minuses);
+       if (qname)
+               free(qname);
+}
+
+static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
+{
+       int fd;
+       unsigned long got;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFLNK:
+               return readlink(path, buf, size);
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("unable to open %s", path);
+               got = 0;
+               for (;;) {
+                       int ret = xread(fd, buf + got, size - got);
+                       if (ret <= 0)
+                               break;
+                       got += ret;
+               }
+               close(fd);
+               return got;
+
+       default:
+               return -1;
+       }
+}
+
+static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines)
+{
+       int i;
+       unsigned long start, backwards, forwards;
+
+       if (fragsize > size)
+               return -1;
+
+       start = 0;
+       if (line > 1) {
+               unsigned long offset = 0;
+               i = line-1;
+               while (offset + fragsize <= size) {
+                       if (buf[offset++] == '\n') {
+                               start = offset;
+                               if (!--i)
+                                       break;
+                       }
+               }
+       }
+
+       /* Exact line number? */
+       if (!memcmp(buf + start, fragment, fragsize))
+               return start;
+
+       /*
+        * There's probably some smart way to do this, but I'll leave
+        * that to the smart and beautiful people. I'm simple and stupid.
+        */
+       backwards = start;
+       forwards = start;
+       for (i = 0; ; i++) {
+               unsigned long try;
+               int n;
+
+               /* "backward" */
+               if (i & 1) {
+                       if (!backwards) {
+                               if (forwards + fragsize > size)
+                                       break;
+                               continue;
+                       }
+                       do {
+                               --backwards;
+                       } while (backwards && buf[backwards-1] != '\n');
+                       try = backwards;
+               } else {
+                       while (forwards + fragsize <= size) {
+                               if (buf[forwards++] == '\n')
+                                       break;
+                       }
+                       try = forwards;
+               }
+
+               if (try + fragsize > size)
+                       continue;
+               if (memcmp(buf + try, fragment, fragsize))
+                       continue;
+               n = (i >> 1)+1;
+               if (i & 1)
+                       n = -n;
+               *lines = n;
+               return try;
+       }
+
+       /*
+        * We should start searching forward and backward.
+        */
+       return -1;
+}
+
+static void remove_first_line(const char **rbuf, int *rsize)
+{
+       const char *buf = *rbuf;
+       int size = *rsize;
+       unsigned long offset;
+       offset = 0;
+       while (offset <= size) {
+               if (buf[offset++] == '\n')
+                       break;
+       }
+       *rsize = size - offset;
+       *rbuf = buf + offset;
+}
+
+static void remove_last_line(const char **rbuf, int *rsize)
+{
+       const char *buf = *rbuf;
+       int size = *rsize;
+       unsigned long offset;
+       offset = size - 1;
+       while (offset > 0) {
+               if (buf[--offset] == '\n')
+                       break;
+       }
+       *rsize = offset + 1;
+}
+
+struct buffer_desc {
+       char *buffer;
+       unsigned long size;
+       unsigned long alloc;
+};
+
+static int apply_line(char *output, const char *patch, int plen)
+{
+       /* plen is number of bytes to be copied from patch,
+        * starting at patch+1 (patch[0] is '+').  Typically
+        * patch[plen] is '\n'.
+        */
+       int add_nl_to_tail = 0;
+       if ((new_whitespace == strip_whitespace) &&
+           1 < plen && isspace(patch[plen-1])) {
+               if (patch[plen] == '\n')
+                       add_nl_to_tail = 1;
+               plen--;
+               while (0 < plen && isspace(patch[plen]))
+                       plen--;
+               applied_after_stripping++;
+       }
+       memcpy(output, patch + 1, plen);
+       if (add_nl_to_tail)
+               output[plen++] = '\n';
+       return plen;
+}
+
+static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
+{
+       int match_beginning, match_end;
+       char *buf = desc->buffer;
+       const char *patch = frag->patch;
+       int offset, size = frag->size;
+       char *old = xmalloc(size);
+       char *new = xmalloc(size);
+       const char *oldlines, *newlines;
+       int oldsize = 0, newsize = 0;
+       unsigned long leading, trailing;
+       int pos, lines;
+
+       while (size > 0) {
+               int len = linelen(patch, size);
+               int plen;
+
+               if (!len)
+                       break;
+
+               /*
+                * "plen" is how much of the line we should use for
+                * the actual patch data. Normally we just remove the
+                * first character on the line, but if the line is
+                * followed by "\ No newline", then we also remove the
+                * last one (which is the newline, of course).
+                */
+               plen = len-1;
+               if (len < size && patch[len] == '\\')
+                       plen--;
+               switch (*patch) {
+               case ' ':
+               case '-':
+                       memcpy(old + oldsize, patch + 1, plen);
+                       oldsize += plen;
+                       if (*patch == '-')
+                               break;
+               /* Fall-through for ' ' */
+               case '+':
+                       if (*patch != '+' || !no_add)
+                               newsize += apply_line(new + newsize, patch,
+                                                     plen);
+                       break;
+               case '@': case '\\':
+                       /* Ignore it, we already handled it */
+                       break;
+               default:
+                       return -1;
+               }
+               patch += len;
+               size -= len;
+       }
+
+#ifdef NO_ACCURATE_DIFF
+       if (oldsize > 0 && old[oldsize - 1] == '\n' &&
+                       newsize > 0 && new[newsize - 1] == '\n') {
+               oldsize--;
+               newsize--;
+       }
+#endif
+
+       oldlines = old;
+       newlines = new;
+       leading = frag->leading;
+       trailing = frag->trailing;
+
+       /*
+        * If we don't have any leading/trailing data in the patch,
+        * we want it to match at the beginning/end of the file.
+        */
+       match_beginning = !leading && (frag->oldpos == 1);
+       match_end = !trailing;
+
+       lines = 0;
+       pos = frag->newpos;
+       for (;;) {
+               offset = find_offset(buf, desc->size, oldlines, oldsize, pos, &lines);
+               if (match_end && offset + oldsize != desc->size)
+                       offset = -1;
+               if (match_beginning && offset)
+                       offset = -1;
+               if (offset >= 0) {
+                       int diff = newsize - oldsize;
+                       unsigned long size = desc->size + diff;
+                       unsigned long alloc = desc->alloc;
+
+                       /* Warn if it was necessary to reduce the number
+                        * of context lines.
+                        */
+                       if ((leading != frag->leading) || (trailing != frag->trailing))
+                               fprintf(stderr, "Context reduced to (%ld/%ld) to apply fragment at %d\n",
+                                       leading, trailing, pos + lines);
+
+                       if (size > alloc) {
+                               alloc = size + 8192;
+                               desc->alloc = alloc;
+                               buf = xrealloc(buf, alloc);
+                               desc->buffer = buf;
+                       }
+                       desc->size = size;
+                       memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+                       memcpy(buf + offset, newlines, newsize);
+                       offset = 0;
+
+                       break;
+               }
+
+               /* Am I at my context limits? */
+               if ((leading <= p_context) && (trailing <= p_context))
+                       break;
+               if (match_beginning || match_end) {
+                       match_beginning = match_end = 0;
+                       continue;
+               }
+               /* Reduce the number of context lines
+                * Reduce both leading and trailing if they are equal
+                * otherwise just reduce the larger context.
+                */
+               if (leading >= trailing) {
+                       remove_first_line(&oldlines, &oldsize);
+                       remove_first_line(&newlines, &newsize);
+                       pos--;
+                       leading--;
+               }
+               if (trailing > leading) {
+                       remove_last_line(&oldlines, &oldsize);
+                       remove_last_line(&newlines, &newsize);
+                       trailing--;
+               }
+       }
+
+       free(old);
+       free(new);
+       return offset;
+}
+
+static char *inflate_it(const void *data, unsigned long size,
+                       unsigned long inflated_size)
+{
+       z_stream stream;
+       void *out;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       stream.next_out = out = xmalloc(inflated_size);
+       stream.avail_out = inflated_size;
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+               free(out);
+               return NULL;
+       }
+       return out;
+}
+
+static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
+{
+       unsigned long dst_size;
+       struct fragment *fragment = patch->fragments;
+       void *data;
+       void *result;
+
+       data = inflate_it(fragment->patch, fragment->size,
+                         patch->deflate_origlen);
+       if (!data)
+               return error("corrupt patch data");
+       switch (patch->is_binary) {
+       case BINARY_DELTA_DEFLATED:
+               result = patch_delta(desc->buffer, desc->size,
+                                    data,
+                                    patch->deflate_origlen,
+                                    &dst_size);
+               free(desc->buffer);
+               desc->buffer = result;
+               free(data);
+               break;
+       case BINARY_LITERAL_DEFLATED:
+               free(desc->buffer);
+               desc->buffer = data;
+               dst_size = patch->deflate_origlen;
+               break;
+       }
+       if (!desc->buffer)
+               return -1;
+       desc->size = desc->alloc = dst_size;
+       return 0;
+}
+
+static int apply_binary(struct buffer_desc *desc, struct patch *patch)
+{
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+       unsigned char sha1[20];
+       unsigned char hdr[50];
+       int hdrlen;
+
+       if (!allow_binary_replacement)
+               return error("cannot apply binary patch to '%s' "
+                            "without --allow-binary-replacement",
+                            name);
+
+       /* For safety, we require patch index line to contain
+        * full 40-byte textual SHA1 for old and new, at least for now.
+        */
+       if (strlen(patch->old_sha1_prefix) != 40 ||
+           strlen(patch->new_sha1_prefix) != 40 ||
+           get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+           get_sha1_hex(patch->new_sha1_prefix, sha1))
+               return error("cannot apply binary patch to '%s' "
+                            "without full index line", name);
+
+       if (patch->old_name) {
+               /* See if the old one matches what the patch
+                * applies to.
+                */
+               write_sha1_file_prepare(desc->buffer, desc->size,
+                                       blob_type, sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+                       return error("the patch applies to '%s' (%s), "
+                                    "which does not match the "
+                                    "current contents.",
+                                    name, sha1_to_hex(sha1));
+       }
+       else {
+               /* Otherwise, the old one must be empty. */
+               if (desc->size)
+                       return error("the patch applies to an empty "
+                                    "'%s' but it is not empty", name);
+       }
+
+       get_sha1_hex(patch->new_sha1_prefix, sha1);
+       if (!memcmp(sha1, null_sha1, 20)) {
+               free(desc->buffer);
+               desc->alloc = desc->size = 0;
+               desc->buffer = NULL;
+               return 0; /* deletion patch */
+       }
+
+       if (has_sha1_file(sha1)) {
+               /* We already have the postimage */
+               char type[10];
+               unsigned long size;
+
+               free(desc->buffer);
+               desc->buffer = read_sha1_file(sha1, type, &size);
+               if (!desc->buffer)
+                       return error("the necessary postimage %s for "
+                                    "'%s' cannot be read",
+                                    patch->new_sha1_prefix, name);
+               desc->alloc = desc->size = size;
+       }
+       else {
+               /* We have verified desc matches the preimage;
+                * apply the patch data to it, which is stored
+                * in the patch->fragments->{patch,size}.
+                */
+               if (apply_binary_fragment(desc, patch))
+                       return error("binary patch does not apply to '%s'",
+                                    name);
+
+               /* verify that the result matches */
+               write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
+                                       sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
+                       return error("binary patch to '%s' creates incorrect result", name);
+       }
+
+       return 0;
+}
+
+static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+{
+       struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+       if (patch->is_binary)
+               return apply_binary(desc, patch);
+
+       while (frag) {
+               if (apply_one_fragment(desc, frag) < 0)
+                       return error("patch failed: %s:%ld",
+                                    name, frag->oldpos);
+               frag = frag->next;
+       }
+       return 0;
+}
+
+static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
+{
+       char *buf;
+       unsigned long size, alloc;
+       struct buffer_desc desc;
+
+       size = 0;
+       alloc = 0;
+       buf = NULL;
+       if (cached) {
+               if (ce) {
+                       char type[20];
+                       buf = read_sha1_file(ce->sha1, type, &size);
+                       if (!buf)
+                               return error("read of %s failed",
+                                            patch->old_name);
+                       alloc = size;
+               }
+       }
+       else if (patch->old_name) {
+               size = st->st_size;
+               alloc = size + 8192;
+               buf = xmalloc(alloc);
+               if (read_old_data(st, patch->old_name, buf, alloc) != size)
+                       return error("read of %s failed", patch->old_name);
+       }
+
+       desc.size = size;
+       desc.alloc = alloc;
+       desc.buffer = buf;
+       if (apply_fragments(&desc, patch) < 0)
+               return -1;
+       patch->result = desc.buffer;
+       patch->resultsize = desc.size;
+
+       if (patch->is_delete && patch->resultsize)
+               return error("removal patch leaves file contents");
+
+       return 0;
+}
+
+static int check_patch(struct patch *patch)
+{
+       struct stat st;
+       const char *old_name = patch->old_name;
+       const char *new_name = patch->new_name;
+       const char *name = old_name ? old_name : new_name;
+       struct cache_entry *ce = NULL;
+
+       if (old_name) {
+               int changed = 0;
+               int stat_ret = 0;
+               unsigned st_mode = 0;
+
+               if (!cached)
+                       stat_ret = lstat(old_name, &st);
+               if (check_index) {
+                       int pos = cache_name_pos(old_name, strlen(old_name));
+                       if (pos < 0)
+                               return error("%s: does not exist in index",
+                                            old_name);
+                       ce = active_cache[pos];
+                       if (stat_ret < 0) {
+                               struct checkout costate;
+                               if (errno != ENOENT)
+                                       return error("%s: %s", old_name,
+                                                    strerror(errno));
+                               /* checkout */
+                               costate.base_dir = "";
+                               costate.base_dir_len = 0;
+                               costate.force = 0;
+                               costate.quiet = 0;
+                               costate.not_new = 0;
+                               costate.refresh_cache = 1;
+                               if (checkout_entry(ce,
+                                                  &costate,
+                                                  NULL) ||
+                                   lstat(old_name, &st))
+                                       return -1;
+                       }
+                       if (!cached)
+                               changed = ce_match_stat(ce, &st, 1);
+                       if (changed)
+                               return error("%s: does not match index",
+                                            old_name);
+                       if (cached)
+                               st_mode = ntohl(ce->ce_mode);
+               }
+               else if (stat_ret < 0)
+                       return error("%s: %s", old_name, strerror(errno));
+
+               if (!cached)
+                       st_mode = ntohl(create_ce_mode(st.st_mode));
+
+               if (patch->is_new < 0)
+                       patch->is_new = 0;
+               if (!patch->old_mode)
+                       patch->old_mode = st_mode;
+               if ((st_mode ^ patch->old_mode) & S_IFMT)
+                       return error("%s: wrong type", old_name);
+               if (st_mode != patch->old_mode)
+                       fprintf(stderr, "warning: %s has type %o, expected %o\n",
+                               old_name, st_mode, patch->old_mode);
+       }
+
+       if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+               if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+                       return error("%s: already exists in index", new_name);
+               if (!cached) {
+                       if (!lstat(new_name, &st))
+                               return error("%s: already exists in working directory", new_name);
+                       if (errno != ENOENT)
+                               return error("%s: %s", new_name, strerror(errno));
+               }
+               if (!patch->new_mode) {
+                       if (patch->is_new)
+                               patch->new_mode = S_IFREG | 0644;
+                       else
+                               patch->new_mode = patch->old_mode;
+               }
+       }
+
+       if (new_name && old_name) {
+               int same = !strcmp(old_name, new_name);
+               if (!patch->new_mode)
+                       patch->new_mode = patch->old_mode;
+               if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
+                       return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+                               patch->new_mode, new_name, patch->old_mode,
+                               same ? "" : " of ", same ? "" : old_name);
+       }
+
+       if (apply_data(patch, &st, ce) < 0)
+               return error("%s: patch does not apply", name);
+       return 0;
+}
+
+static int check_patch_list(struct patch *patch)
+{
+       int error = 0;
+
+       for (;patch ; patch = patch->next)
+               error |= check_patch(patch);
+       return error;
+}
+
+static inline int is_null_sha1(const unsigned char *sha1)
+{
+       return !memcmp(sha1, null_sha1, 20);
+}
+
+static void show_index_list(struct patch *list)
+{
+       struct patch *patch;
+
+       /* Once we start supporting the reverse patch, it may be
+        * worth showing the new sha1 prefix, but until then...
+        */
+       for (patch = list; patch; patch = patch->next) {
+               const unsigned char *sha1_ptr;
+               unsigned char sha1[20];
+               const char *name;
+
+               name = patch->old_name ? patch->old_name : patch->new_name;
+               if (patch->is_new)
+                       sha1_ptr = null_sha1;
+               else if (get_sha1(patch->old_sha1_prefix, sha1))
+                       die("sha1 information is lacking or useless (%s).",
+                           name);
+               else
+                       sha1_ptr = sha1;
+
+               printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
+               if (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar(line_termination);
+       }
+}
+
+static void stat_patch_list(struct patch *patch)
+{
+       int files, adds, dels;
+
+       for (files = adds = dels = 0 ; patch ; patch = patch->next) {
+               files++;
+               adds += patch->lines_added;
+               dels += patch->lines_deleted;
+               show_stats(patch);
+       }
+
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+}
+
+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 (line_termination && quote_c_style(name, NULL, NULL, 0))
+                       quote_c_style(name, NULL, stdout, 0);
+               else
+                       fputs(name, stdout);
+               putchar('\n');
+       }
+}
+
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+       if (mode)
+               printf(" %s mode %06o %s\n", newdelete, mode, name);
+       else
+               printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+       if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+               if (show_name)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->old_mode, p->new_mode, p->new_name);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->old_mode, p->new_mode);
+       }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+       const char *renamecopy = p->is_rename ? "rename" : "copy";
+       const char *old, *new;
+
+       /* Find common prefix */
+       old = p->old_name;
+       new = p->new_name;
+       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->old_name thru old is the common prefix, and old and new
+        * through the end of names are renames
+        */
+       if (old != p->old_name)
+               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+                      (int)(old - p->old_name), p->old_name,
+                      old, new, p->score);
+       else
+               printf(" %s %s => %s (%d%%)\n", renamecopy,
+                      p->old_name, p->new_name, p->score);
+       show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+       struct patch *p;
+
+       for (p = patch; p; p = p->next) {
+               if (p->is_new)
+                       show_file_mode_name("create", p->new_mode, p->new_name);
+               else if (p->is_delete)
+                       show_file_mode_name("delete", p->old_mode, p->old_name);
+               else {
+                       if (p->is_rename || p->is_copy)
+                               show_rename_copy(p);
+                       else {
+                               if (p->score) {
+                                       printf(" rewrite %s (%d%%)\n",
+                                              p->new_name, p->score);
+                                       show_mode_change(p, 0);
+                               }
+                               else
+                                       show_mode_change(p, 1);
+                       }
+               }
+       }
+}
+
+static void patch_stats(struct patch *patch)
+{
+       int lines = patch->lines_added + patch->lines_deleted;
+
+       if (lines > max_change)
+               max_change = lines;
+       if (patch->old_name) {
+               int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->old_name);
+               if (len > max_len)
+                       max_len = len;
+       }
+       if (patch->new_name) {
+               int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+               if (!len)
+                       len = strlen(patch->new_name);
+               if (len > max_len)
+                       max_len = len;
+       }
+}
+
+static void remove_file(struct patch *patch)
+{
+       if (write_index) {
+               if (remove_file_from_cache(patch->old_name) < 0)
+                       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);
+}
+
+static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
+{
+       struct stat st;
+       struct cache_entry *ce;
+       int namelen = strlen(path);
+       unsigned ce_size = cache_entry_size(namelen);
+
+       if (!write_index)
+               return;
+
+       ce = xcalloc(1, ce_size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = htons(namelen);
+       if (!cached) {
+               if (lstat(path, &st) < 0)
+                       die("unable to stat newly created file %s", path);
+               fill_stat_cache_info(ce, &st);
+       }
+       if (write_sha1_file(buf, size, blob_type, ce->sha1) < 0)
+               die("unable to create backing store for newly created file %s", path);
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+               die("unable to add cache entry for %s", path);
+}
+
+static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
+{
+       int fd;
+
+       if (S_ISLNK(mode))
+               return symlink(buf, path);
+       fd = open(path, O_CREAT | O_EXCL | O_WRONLY, (mode & 0100) ? 0777 : 0666);
+       if (fd < 0)
+               return -1;
+       while (size) {
+               int written = xwrite(fd, buf, size);
+               if (written < 0)
+                       die("writing file %s: %s", path, strerror(errno));
+               if (!written)
+                       die("out of space writing file %s", path);
+               buf += written;
+               size -= written;
+       }
+       if (close(fd) < 0)
+               die("closing file %s: %s", path, strerror(errno));
+       return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * which is true 99% of the time anyway. If they don't,
+ * we create them and try again.
+ */
+static void create_one_file(char *path, unsigned mode, const char *buf, unsigned long size)
+{
+       if (cached)
+               return;
+       if (!try_create_file(path, mode, buf, size))
+               return;
+
+       if (errno == ENOENT) {
+               if (safe_create_leading_directories(path))
+                       return;
+               if (!try_create_file(path, mode, buf, size))
+                       return;
+       }
+
+       if (errno == EEXIST) {
+               unsigned int nr = getpid();
+
+               for (;;) {
+                       const char *newpath;
+                       newpath = mkpath("%s~%u", path, nr);
+                       if (!try_create_file(newpath, mode, buf, size)) {
+                               if (!rename(newpath, path))
+                                       return;
+                               unlink(newpath);
+                               break;
+                       }
+                       if (errno != EEXIST)
+                               break;
+                       ++nr;
+               }
+       }
+       die("unable to write file %s mode %o", path, mode);
+}
+
+static void create_file(struct patch *patch)
+{
+       char *path = patch->new_name;
+       unsigned mode = patch->new_mode;
+       unsigned long size = patch->resultsize;
+       char *buf = patch->result;
+
+       if (!mode)
+               mode = S_IFREG | 0644;
+       create_one_file(path, mode, buf, size);
+       add_index_file(path, mode, buf, size);
+       cache_tree_invalidate_path(active_cache_tree, path);
+}
+
+static void write_out_one_result(struct patch *patch)
+{
+       if (patch->is_delete > 0) {
+               remove_file(patch);
+               return;
+       }
+       if (patch->is_new > 0 || patch->is_copy) {
+               create_file(patch);
+               return;
+       }
+       /*
+        * Rename or modification boils down to the same
+        * thing: remove the old, write the new
+        */
+       remove_file(patch);
+       create_file(patch);
+}
+
+static void write_out_results(struct patch *list, int skipped_patch)
+{
+       if (!list && !skipped_patch)
+               die("No changes");
+
+       while (list) {
+               write_out_one_result(list);
+               list = list->next;
+       }
+}
+
+static struct lock_file lock_file;
+
+static struct excludes {
+       struct excludes *next;
+       const char *path;
+} *excludes;
+
+static int use_patch(struct patch *p)
+{
+       const char *pathname = p->new_name ? p->new_name : p->old_name;
+       struct excludes *x = excludes;
+       while (x) {
+               if (fnmatch(x->path, pathname, 0) == 0)
+                       return 0;
+               x = x->next;
+       }
+       if (0 < prefix_length) {
+               int pathlen = strlen(pathname);
+               if (pathlen <= prefix_length ||
+                   memcmp(prefix, pathname, prefix_length))
+                       return 0;
+       }
+       return 1;
+}
+
+static int apply_patch(int fd, const char *filename)
+{
+       unsigned long offset, size;
+       char *buffer = read_patch_file(fd, &size);
+       struct patch *list = NULL, **listp = &list;
+       int skipped_patch = 0;
+
+       patch_input_file = filename;
+       if (!buffer)
+               return -1;
+       offset = 0;
+       while (size > 0) {
+               struct patch *patch;
+               int nr;
+
+               patch = xcalloc(1, sizeof(*patch));
+               nr = parse_chunk(buffer + offset, size, patch);
+               if (nr < 0)
+                       break;
+               if (use_patch(patch)) {
+                       patch_stats(patch);
+                       *listp = patch;
+                       listp = &patch->next;
+               } else {
+                       /* perhaps free it a bit better? */
+                       free(patch);
+                       skipped_patch++;
+               }
+               offset += nr;
+               size -= nr;
+       }
+
+       if (whitespace_error && (new_whitespace == error_on_whitespace))
+               apply = 0;
+
+       write_index = check_index && apply;
+       if (write_index && newfd < 0) {
+               newfd = hold_lock_file_for_update(&lock_file,
+                                                 get_index_file());
+               if (newfd < 0)
+                       die("unable to create new index file");
+       }
+       if (check_index) {
+               if (read_cache() < 0)
+                       die("unable to read index file");
+       }
+
+       if ((check || apply) && check_patch_list(list) < 0)
+               exit(1);
+
+       if (apply)
+               write_out_results(list, skipped_patch);
+
+       if (show_index_info)
+               show_index_list(list);
+
+       if (diffstat)
+               stat_patch_list(list);
+
+       if (numstat)
+               numstat_patch_list(list);
+
+       if (summary)
+               summary_patch_list(list);
+
+       free(buffer);
+       return 0;
+}
+
+static int git_apply_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "apply.whitespace")) {
+               apply_default_whitespace = strdup(value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+
+int cmd_apply(int argc, const char **argv, char **envp)
+{
+       int i;
+       int read_stdin = 1;
+       const char *whitespace_option = NULL;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               char *end;
+               int fd;
+
+               if (!strcmp(arg, "-")) {
+                       apply_patch(0, "<stdin>");
+                       read_stdin = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "--exclude=", 10)) {
+                       struct excludes *x = xmalloc(sizeof(*x));
+                       x->path = arg + 10;
+                       x->next = excludes;
+                       excludes = x;
+                       continue;
+               }
+               if (!strncmp(arg, "-p", 2)) {
+                       p_value = atoi(arg + 2);
+                       continue;
+               }
+               if (!strcmp(arg, "--no-add")) {
+                       no_add = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--stat")) {
+                       apply = 0;
+                       diffstat = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--allow-binary-replacement") ||
+                   !strcmp(arg, "--binary")) {
+                       allow_binary_replacement = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--numstat")) {
+                       apply = 0;
+                       numstat = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--summary")) {
+                       apply = 0;
+                       summary = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--check")) {
+                       apply = 0;
+                       check = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--index")) {
+                       check_index = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cached")) {
+                       check_index = 1;
+                       cached = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--apply")) {
+                       apply = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--index-info")) {
+                       apply = 0;
+                       show_index_info = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "-C", 2)) {
+                       p_context = strtoul(arg + 2, &end, 0);
+                       if (*end != '\0')
+                               die("unrecognized context count '%s'", arg + 2);
+                       continue;
+               }
+               if (!strncmp(arg, "--whitespace=", 13)) {
+                       whitespace_option = arg + 13;
+                       parse_whitespace_option(arg + 13);
+                       continue;
+               }
+
+               if (check_index && prefix_length < 0) {
+                       prefix = setup_git_directory();
+                       prefix_length = prefix ? strlen(prefix) : 0;
+                       git_config(git_apply_config);
+                       if (!whitespace_option && apply_default_whitespace)
+                               parse_whitespace_option(apply_default_whitespace);
+               }
+               if (0 < prefix_length)
+                       arg = prefix_filename(prefix, prefix_length, arg);
+
+               fd = open(arg, O_RDONLY);
+               if (fd < 0)
+                       usage(apply_usage);
+               read_stdin = 0;
+               set_default_whitespace_mode(whitespace_option);
+               apply_patch(fd, arg);
+               close(fd);
+       }
+       set_default_whitespace_mode(whitespace_option);
+       if (read_stdin)
+               apply_patch(0, "<stdin>");
+       if (whitespace_error) {
+               if (squelch_whitespace_errors &&
+                   squelch_whitespace_errors < whitespace_error) {
+                       int squelched =
+                               whitespace_error - squelch_whitespace_errors;
+                       fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+                               squelched,
+                               squelched == 1 ? "" : "s");
+               }
+               if (new_whitespace == error_on_whitespace)
+                       die("%d line%s add%s trailing whitespaces.",
+                           whitespace_error,
+                           whitespace_error == 1 ? "" : "s",
+                           whitespace_error == 1 ? "s" : "");
+               if (applied_after_stripping)
+                       fprintf(stderr, "warning: %d line%s applied after"
+                               " stripping trailing whitespaces.\n",
+                               applied_after_stripping,
+                               applied_after_stripping == 1 ? "" : "s");
+               else if (whitespace_error)
+                       fprintf(stderr, "warning: %d line%s add%s trailing"
+                               " whitespaces.\n",
+                               whitespace_error,
+                               whitespace_error == 1 ? "" : "s",
+                               whitespace_error == 1 ? "s" : "");
+       }
+
+       if (write_index) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_lock_file(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin-cat-file.c b/builtin-cat-file.c
new file mode 100644 (file)
index 0000000..4d36817
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "exec_cmd.h"
+#include "tag.h"
+#include "tree.h"
+#include "builtin.h"
+
+static void flush_buffer(const char *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("git-cat-file: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-cat-file: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+       /* the parser in tag.c is useless here. */
+       const char *endp = buf + size;
+       const char *cp = buf;
+
+       while (cp < endp) {
+               char c = *cp++;
+               if (c != '\n')
+                       continue;
+               if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+                       const char *tagger = cp;
+
+                       /* Found the tagger line.  Copy out the contents
+                        * of the buffer so far.
+                        */
+                       flush_buffer(buf, cp - buf);
+
+                       /*
+                        * Do something intelligent, like pretty-printing
+                        * the date.
+                        */
+                       while (cp < endp) {
+                               if (*cp++ == '\n') {
+                                       /* tagger to cp is a line
+                                        * that has ident and time.
+                                        */
+                                       const char *sp = tagger;
+                                       char *ep;
+                                       unsigned long date;
+                                       long tz;
+                                       while (sp < cp && *sp != '>')
+                                               sp++;
+                                       if (sp == cp) {
+                                               /* give up */
+                                               flush_buffer(tagger,
+                                                            cp - tagger);
+                                               break;
+                                       }
+                                       while (sp < cp &&
+                                              !('0' <= *sp && *sp <= '9'))
+                                               sp++;
+                                       flush_buffer(tagger, sp - tagger);
+                                       date = strtoul(sp, &ep, 10);
+                                       tz = strtol(ep, NULL, 10);
+                                       sp = show_date(date, tz);
+                                       flush_buffer(sp, strlen(sp));
+                                       xwrite(1, "\n", 1);
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               if (cp < endp && *cp == '\n')
+                       /* end of header */
+                       break;
+       }
+       /* At this point, we have copied out the header up to the end of
+        * the tagger line and cp points at one past \n.  It could be the
+        * next header line after the tagger line, or it could be another
+        * \n that marks the end of the headers.  We need to copy out the
+        * remainder as is.
+        */
+       if (cp < endp)
+               flush_buffer(cp, endp - cp);
+       return 0;
+}
+
+int cmd_cat_file(int argc, const char **argv, char **envp)
+{
+       unsigned char sha1[20];
+       char type[20];
+       void *buf;
+       unsigned long size;
+       int opt;
+
+       setup_git_directory();
+       git_config(git_default_config);
+       if (argc != 3)
+               usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
+       if (get_sha1(argv[2], sha1))
+               die("Not a valid object name %s", argv[2]);
+
+       opt = 0;
+       if ( argv[1][0] == '-' ) {
+               opt = argv[1][1];
+               if ( !opt || argv[1][2] )
+                       opt = -1; /* Not a single character option */
+       }
+
+       buf = NULL;
+       switch (opt) {
+       case 't':
+               if (!sha1_object_info(sha1, type, NULL)) {
+                       printf("%s\n", type);
+                       return 0;
+               }
+               break;
+
+       case 's':
+               if (!sha1_object_info(sha1, type, &size)) {
+                       printf("%lu\n", size);
+                       return 0;
+               }
+               break;
+
+       case 'e':
+               return !has_sha1_file(sha1);
+
+       case 'p':
+               if (sha1_object_info(sha1, type, NULL))
+                       die("Not a valid object name %s", argv[2]);
+
+               /* custom pretty-print here */
+               if (!strcmp(type, tree_type))
+                       return cmd_ls_tree(2, argv + 1, NULL);
+
+               buf = read_sha1_file(sha1, type, &size);
+               if (!buf)
+                       die("Cannot read object %s", argv[2]);
+               if (!strcmp(type, tag_type))
+                       return pprint_tag(sha1, buf, size);
+
+               /* otherwise just spit out the data */
+               break;
+       case 0:
+               buf = read_object_with_reference(sha1, argv[1], &size, NULL);
+               break;
+
+       default:
+               die("git-cat-file: unknown option: %s\n", argv[1]);
+       }
+
+       if (!buf)
+               die("git-cat-file %s: bad file", argv[2]);
+
+       flush_buffer(buf, size);
+       return 0;
+}
diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c
new file mode 100644 (file)
index 0000000..4a23936
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+#include "builtin.h"
+
+int cmd_check_ref_format(int argc, const char **argv, char **envp)
+{
+       if (argc != 2)
+               usage("git check-ref-format refname");
+       return !!check_ref_format(argv[1]);
+}
diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c
new file mode 100644 (file)
index 0000000..ec082bf
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "builtin.h"
+
+#define BLOCKING (1ul << 14)
+
+/*
+ * FIXME! Share the code with "write-tree.c"
+ */
+static void init_buffer(char **bufp, unsigned int *sizep)
+{
+       char *buf = xmalloc(BLOCKING);
+       *sizep = 0;
+       *bufp = buf;
+}
+
+static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+{
+       char one_line[2048];
+       va_list args;
+       int len;
+       unsigned long alloc, size, newsize;
+       char *buf;
+
+       va_start(args, fmt);
+       len = vsnprintf(one_line, sizeof(one_line), fmt, args);
+       va_end(args);
+       size = *sizep;
+       newsize = size + len;
+       alloc = (size + 32767) & ~32767;
+       buf = *bufp;
+       if (newsize > alloc) {
+               alloc = (newsize + 32767) & ~32767;
+               buf = xrealloc(buf, alloc);
+               *bufp = buf;
+       }
+       *sizep = newsize;
+       memcpy(buf + size, one_line, len);
+}
+
+static void check_valid(unsigned char *sha1, const char *expect)
+{
+       char type[20];
+
+       if (sha1_object_info(sha1, type, NULL))
+               die("%s is not a valid object", sha1_to_hex(sha1));
+       if (expect && strcmp(type, expect))
+               die("%s is not a valid '%s' object", sha1_to_hex(sha1),
+                   expect);
+}
+
+/*
+ * Having more than two parents is not strange at all, and this is
+ * how multi-way merges are represented.
+ */
+#define MAXPARENT (16)
+static unsigned char parent_sha1[MAXPARENT][20];
+
+static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static int new_parent(int idx)
+{
+       int i;
+       unsigned char *sha1 = parent_sha1[idx];
+       for (i = 0; i < idx; i++) {
+               if (!memcmp(parent_sha1[i], sha1, 20)) {
+                       error("duplicate parent %s ignored", sha1_to_hex(sha1));
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+int cmd_commit_tree(int argc, const char **argv, char **envp)
+{
+       int i;
+       int parents = 0;
+       unsigned char tree_sha1[20];
+       unsigned char commit_sha1[20];
+       char comment[1000];
+       char *buffer;
+       unsigned int size;
+
+       setup_ident();
+       setup_git_directory();
+
+       git_config(git_default_config);
+
+       if (argc < 2)
+               usage(commit_tree_usage);
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       check_valid(tree_sha1, tree_type);
+       for (i = 2; i < argc; i += 2) {
+               const char *a, *b;
+               a = argv[i]; b = argv[i+1];
+               if (!b || strcmp(a, "-p"))
+                       usage(commit_tree_usage);
+               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]);
+
+       init_buffer(&buffer, &size);
+       add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
+
+       /*
+        * NOTE! This ordering means that the same exact tree merged with a
+        * different order of parents will be a _different_ changeset even
+        * if everything else stays the same.
+        */
+       for (i = 0; i < parents; i++)
+               add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
+
+       /* 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));
+
+       /* And add the comment */
+       while (fgets(comment, sizeof(comment), stdin) != NULL)
+               add_buffer(&buffer, &size, "%s", comment);
+
+       if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
+               printf("%s\n", sha1_to_hex(commit_sha1));
+               return 0;
+       }
+       else
+               return 1;
+}
diff --git a/builtin-count.c b/builtin-count.c
new file mode 100644 (file)
index 0000000..5ee72df
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Builtin "git count-objects".
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+
+static const char count_objects_usage[] = "git-count-objects [-v]";
+
+static void count_objects(DIR *d, char *path, int len, int verbose,
+                         unsigned long *loose,
+                         unsigned long *loose_size,
+                         unsigned long *packed_loose,
+                         unsigned long *garbage)
+{
+       struct dirent *ent;
+       while ((ent = readdir(d)) != NULL) {
+               char hex[41];
+               unsigned char sha1[20];
+               const char *cp;
+               int bad = 0;
+
+               if ((ent->d_name[0] == '.') &&
+                   (ent->d_name[1] == 0 ||
+                    ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+                       continue;
+               for (cp = ent->d_name; *cp; cp++) {
+                       int ch = *cp;
+                       if (('0' <= ch && ch <= '9') ||
+                           ('a' <= ch && ch <= 'f'))
+                               continue;
+                       bad = 1;
+                       break;
+               }
+               if (cp - ent->d_name != 38)
+                       bad = 1;
+               else {
+                       struct stat st;
+                       memcpy(path + len + 3, ent->d_name, 38);
+                       path[len + 2] = '/';
+                       path[len + 41] = 0;
+                       if (lstat(path, &st) || !S_ISREG(st.st_mode))
+                               bad = 1;
+                       else
+                               (*loose_size) += st.st_blocks;
+               }
+               if (bad) {
+                       if (verbose) {
+                               error("garbage found: %.*s/%s",
+                                     len + 2, path, ent->d_name);
+                               (*garbage)++;
+                       }
+                       continue;
+               }
+               (*loose)++;
+               if (!verbose)
+                       continue;
+               memcpy(hex, path+len, 2);
+               memcpy(hex+2, ent->d_name, 38);
+               hex[40] = 0;
+               if (get_sha1_hex(hex, sha1))
+                       die("internal error");
+               if (has_sha1_pack(sha1))
+                       (*packed_loose)++;
+       }
+}
+
+int cmd_count_objects(int ac, const char **av, char **ep)
+{
+       int i;
+       int verbose = 0;
+       const char *objdir = get_object_directory();
+       int len = strlen(objdir);
+       char *path = xmalloc(len + 50);
+       unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
+       unsigned long loose_size = 0;
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               if (*arg != '-')
+                       break;
+               else if (!strcmp(arg, "-v"))
+                       verbose = 1;
+               else
+                       usage(count_objects_usage);
+       }
+
+       /* we do not take arguments other than flags for now */
+       if (i < ac)
+               usage(count_objects_usage);
+       memcpy(path, objdir, len);
+       if (len && objdir[len-1] != '/')
+               path[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+               sprintf(path + len, "%02x", i);
+               d = opendir(path);
+               if (!d)
+                       continue;
+               count_objects(d, path, len, verbose,
+                             &loose, &loose_size, &packed_loose, &garbage);
+               closedir(d);
+       }
+       if (verbose) {
+               struct packed_git *p;
+               if (!packed_git)
+                       prepare_packed_git();
+               for (p = packed_git; p; p = p->next) {
+                       if (!p->pack_local)
+                               continue;
+                       packed += num_packed_objects(p);
+               }
+               printf("count: %lu\n", loose);
+               printf("size: %lu\n", loose_size / 2);
+               printf("in-pack: %lu\n", packed);
+               printf("prune-packable: %lu\n", packed_loose);
+               printf("garbage: %lu\n", garbage);
+       }
+       else
+               printf("%lu objects, %lu kilobytes\n",
+                      loose, loose_size / 2);
+       return 0;
+}
diff --git a/builtin-diff-files.c b/builtin-diff-files.c
new file mode 100644 (file)
index 0000000..cebda82
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_files_usage[] =
+"git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_files(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+       int silent = 0;
+
+       git_config(git_diff_config);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       while (1 < argc && argv[1][0] == '-') {
+               if (!strcmp(argv[1], "--base"))
+                       rev.max_count = 1;
+               else if (!strcmp(argv[1], "--ours"))
+                       rev.max_count = 2;
+               else if (!strcmp(argv[1], "--theirs"))
+                       rev.max_count = 3;
+               else if (!strcmp(argv[1], "-q"))
+                       silent = 1;
+               else
+                       usage(diff_files_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there are NO revision (i.e. pending object) parameter,
+        * rev.max_count is reasonable (0 <= n <= 3),
+        * there is no other revision filtering parameters.
+        */
+       if (rev.pending_objects ||
+           rev.min_age != -1 || rev.max_age != -1)
+               usage(diff_files_usage);
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
+        */
+       if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return run_diff_files(&rev, silent);
+}
diff --git a/builtin-diff-index.c b/builtin-diff-index.c
new file mode 100644 (file)
index 0000000..1958580
--- /dev/null
@@ -0,0 +1,39 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+#include "builtin.h"
+
+static const char diff_cache_usage[] =
+"git-diff-index [-m] [--cached] "
+"[<common diff options>] <tree-ish> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_index(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+       int cached = 0;
+       int i;
+
+       git_config(git_diff_config);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+                       
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else
+                       usage(diff_cache_usage);
+       }
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!rev.pending_objects || rev.pending_objects->next ||
+           rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
+               usage(diff_cache_usage);
+       return run_diff_index(&rev, cached);
+}
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
new file mode 100644 (file)
index 0000000..7c157ca
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * 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 (memcmp(one->sha1, two->sha1, 20) ||
+                        (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, char **envp)
+{
+       int stage1, stage2;
+       const char *prefix = setup_git_directory();
+       const char **pathspec = NULL;
+
+       git_config(git_diff_config);
+       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 (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;
+}
diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c
new file mode 100644 (file)
index 0000000..58cf658
--- /dev/null
@@ -0,0 +1,152 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+static struct rev_info log_tree_opt;
+
+static int diff_tree_commit_sha1(const unsigned char *sha1)
+{
+       struct commit *commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return -1;
+       return log_tree_commit(&log_tree_opt, commit);
+}
+
+static int diff_tree_stdin(char *line)
+{
+       int len = strlen(line);
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (!len || line[len-1] != '\n')
+               return -1;
+       line[len-1] = 0;
+       if (get_sha1_hex(line, sha1))
+               return -1;
+       commit = lookup_commit(sha1);
+       if (!commit || parse_commit(commit))
+               return -1;
+       if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
+               /* Graft the fake parents locally to the commit */
+               int pos = 41;
+               struct commit_list **pptr, *parents;
+
+               /* Free the real parent list */
+               for (parents = commit->parents; parents; ) {
+                       struct commit_list *tmp = parents->next;
+                       free(parents);
+                       parents = tmp;
+               }
+               commit->parents = NULL;
+               pptr = &(commit->parents);
+               while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
+                       struct commit *parent = lookup_commit(sha1);
+                       if (parent) {
+                               pptr = &commit_list_insert(parent, pptr)->next;
+                       }
+                       pos += 41;
+               }
+       }
+       return log_tree_commit(&log_tree_opt, commit);
+}
+
+static const char diff_tree_usage[] =
+"git-diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+"  -r            diff recursively\n"
+"  --root        include the initial commit as diff against /dev/null\n"
+COMMON_DIFF_OPTIONS_HELP;
+
+int cmd_diff_tree(int argc, const char **argv, char **envp)
+{
+       int nr_sha1;
+       char line[1000];
+       struct object *tree1, *tree2;
+       static struct rev_info *opt = &log_tree_opt;
+       struct object_list *list;
+       int read_stdin = 0;
+
+       git_config(git_diff_config);
+       nr_sha1 = 0;
+       init_revisions(opt);
+       opt->abbrev = 0;
+       opt->diff = 1;
+       argc = setup_revisions(argc, argv, opt, NULL);
+
+       while (--argc > 0) {
+               const char *arg = *++argv;
+
+               if (!strcmp(arg, "--stdin")) {
+                       read_stdin = 1;
+                       continue;
+               }
+               usage(diff_tree_usage);
+       }
+
+       /*
+        * NOTE! "setup_revisions()" will have inserted the revisions
+        * it parsed in reverse order. So if you do
+        *
+        *      git-diff-tree a b
+        *
+        * the commit list will be "b" -> "a" -> NULL, so we reverse
+        * the order of the objects if the first one is not marked
+        * UNINTERESTING.
+        */
+       nr_sha1 = 0;
+       list = opt->pending_objects;
+       if (list) {
+               nr_sha1++;
+               tree1 = list->item;
+               list = list->next;
+               if (list) {
+                       nr_sha1++;
+                       tree2 = tree1;
+                       tree1 = list->item;
+                       if (list->next)
+                               usage(diff_tree_usage);
+                       /* Switch them around if the second one was uninteresting.. */
+                       if (tree2->flags & UNINTERESTING) {
+                               struct object *tmp = tree2;
+                               tree2 = tree1;
+                               tree1 = tmp;
+                       }
+               }
+       }
+
+       switch (nr_sha1) {
+       case 0:
+               if (!read_stdin)
+                       usage(diff_tree_usage);
+               break;
+       case 1:
+               diff_tree_commit_sha1(tree1->sha1);
+               break;
+       case 2:
+               diff_tree_sha1(tree1->sha1,
+                              tree2->sha1,
+                              "", &opt->diffopt);
+               log_tree_diff_flush(opt);
+               break;
+       }
+
+       if (!read_stdin)
+               return 0;
+
+       if (opt->diffopt.detect_rename)
+               opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+                                      DIFF_SETUP_USE_CACHE);
+       while (fgets(line, sizeof(line), stdin)) {
+               unsigned char sha1[20];
+
+               if (get_sha1_hex(line, sha1)) {
+                       fputs(line, stdout);
+                       fflush(stdout);
+               }
+               else
+                       diff_tree_stdin(line);
+       }
+       return 0;
+}
diff --git a/builtin-diff.c b/builtin-diff.c
new file mode 100644 (file)
index 0000000..27451d5
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ * Builtin "git diff"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "blob.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+/* NEEDSWORK: struct object has place for name but we _do_
+ * know mode when we extracted the blob out of a tree, which
+ * we currently lose.
+ */
+struct blobinfo {
+       unsigned char sha1[20];
+       const char *name;
+};
+
+static const char builtin_diff_usage[] =
+"diff <options> <rev>{0,2} -- <path>*";
+
+static int builtin_diff_files(struct rev_info *revs,
+                             int argc, const char **argv)
+{
+       int silent = 0;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--base"))
+                       revs->max_count = 1;
+               else if (!strcmp(arg, "--ours"))
+                       revs->max_count = 2;
+               else if (!strcmp(arg, "--theirs"))
+                       revs->max_count = 3;
+               else if (!strcmp(arg, "-q"))
+                       silent = 1;
+               else if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there are NO revision (i.e. pending object) parameter,
+        * specified rev.max_count is reasonable (0 <= n <= 3), and
+        * there is no other revision filtering parameter.
+        */
+       if (revs->pending_objects ||
+           revs->min_age != -1 ||
+           revs->max_age != -1 ||
+           3 < revs->max_count)
+               usage(builtin_diff_usage);
+       if (revs->max_count < 0 &&
+           (revs->diffopt.output_format == DIFF_FORMAT_PATCH))
+               revs->combine_merges = revs->dense_combined_merges = 1;
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
+        */
+       if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               revs->diffopt.output_format = DIFF_FORMAT_RAW;
+       return run_diff_files(revs, silent);
+}
+
+static void stuff_change(struct diff_options *opt,
+                        unsigned old_mode, unsigned new_mode,
+                        const unsigned char *old_sha1,
+                        const unsigned char *new_sha1,
+                        const char *old_name,
+                        const char *new_name)
+{
+       struct diff_filespec *one, *two;
+
+       if (memcmp(null_sha1, old_sha1, 20) &&
+           memcmp(null_sha1, new_sha1, 20) &&
+           !memcmp(old_sha1, new_sha1, 20))
+               return;
+
+       if (opt->reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_u;
+               const char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
+               tmp_c = old_name; old_name = new_name; new_name = tmp_c;
+       }
+       one = alloc_filespec(old_name);
+       two = alloc_filespec(new_name);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       /* NEEDSWORK: shouldn't this part of diffopt??? */
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+static int builtin_diff_b_f(struct rev_info *revs,
+                           int argc, const char **argv,
+                           struct blobinfo *blob,
+                           const char *path)
+{
+       /* Blob vs file in the working tree*/
+       struct stat st;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (lstat(path, &st))
+               die("'%s': %s", path, strerror(errno));
+       if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
+               die("'%s': not a regular file or symlink", path);
+       stuff_change(&revs->diffopt,
+                    canon_mode(st.st_mode), canon_mode(st.st_mode),
+                    blob[0].sha1, null_sha1,
+                    path, path);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_blobs(struct rev_info *revs,
+                             int argc, const char **argv,
+                             struct blobinfo *blob)
+{
+       /* Blobs: the arguments are reversed when setup_revisions()
+        * picked them up.
+        */
+       unsigned mode = canon_mode(S_IFREG | 0644);
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       stuff_change(&revs->diffopt,
+                    mode, mode,
+                    blob[1].sha1, blob[0].sha1,
+                    blob[0].name, blob[0].name);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_index(struct rev_info *revs,
+                             int argc, const char **argv)
+{
+       int cached = 0;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!revs->pending_objects || revs->pending_objects->next ||
+           revs->max_count != -1 || revs->min_age != -1 ||
+           revs->max_age != -1)
+               usage(builtin_diff_usage);
+       return run_diff_index(revs, cached);
+}
+
+static int builtin_diff_tree(struct rev_info *revs,
+                            int argc, const char **argv,
+                            struct object_list *ent)
+{
+       const unsigned char *(sha1[2]);
+       int swap = 1;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+
+       /* We saw two trees, ent[0] and ent[1].
+        * unless ent[0] is unintesting, they are swapped
+        */
+       if (ent[0].item->flags & UNINTERESTING)
+               swap = 0;
+       sha1[swap] = ent[0].item->sha1;
+       sha1[1-swap] = ent[1].item->sha1;
+       diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
+       log_tree_diff_flush(revs);
+       return 0;
+}
+
+static int builtin_diff_combined(struct rev_info *revs,
+                                int argc, const char **argv,
+                                struct object_list *ent,
+                                int ents)
+{
+       const unsigned char (*parent)[20];
+       int i;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (!revs->dense_combined_merges && !revs->combine_merges)
+               revs->dense_combined_merges = revs->combine_merges = 1;
+       parent = xmalloc(ents * sizeof(*parent));
+       /* Again, the revs are all reverse */
+       for (i = 0; i < ents; i++)
+               memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);
+       diff_tree_combined(parent[0], parent + 1, ents - 1,
+                          revs->dense_combined_merges, revs);
+       return 0;
+}
+
+void add_head(struct rev_info *revs)
+{
+       unsigned char sha1[20];
+       struct object *obj;
+       if (get_sha1("HEAD", sha1))
+               return;
+       obj = parse_object(sha1);
+       if (!obj)
+               return;
+       add_object(obj, &revs->pending_objects, NULL, "HEAD");
+}
+
+int cmd_diff(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+       struct object_list *list, ent[100];
+       int ents = 0, blobs = 0, paths = 0;
+       const char *path = NULL;
+       struct blobinfo blob[2];
+
+       /*
+        * We could get N tree-ish in the rev.pending_objects list.
+        * Also there could be M blobs there, and P pathspecs.
+        *
+        * N=0, M=0:
+        *      cache vs files (diff-files)
+        * N=0, M=2:
+        *      compare two random blobs.  P must be zero.
+        * N=0, M=1, P=1:
+        *      compare a blob with a working tree file.
+        *
+        * N=1, M=0:
+        *      tree vs cache (diff-index --cached)
+        *
+        * N=2, M=0:
+        *      tree vs tree (diff-tree)
+        *
+        * Other cases are errors.
+        */
+
+       git_config(git_diff_config);
+       init_revisions(&rev);
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       /* Do we have --cached and not have a pending object, then
+        * default to HEAD by hand.  Eek.
+        */
+       if (!rev.pending_objects) {
+               int i;
+               for (i = 1; i < argc; i++) {
+                       const char *arg = argv[i];
+                       if (!strcmp(arg, "--"))
+                               break;
+                       else if (!strcmp(arg, "--cached")) {
+                               add_head(&rev);
+                               break;
+                       }
+               }
+       }
+
+       for (list = rev.pending_objects; list; list = list->next) {
+               struct object *obj = list->item;
+               const char *name = list->name;
+               int flags = (obj->flags & UNINTERESTING);
+               if (!obj->parsed)
+                       obj = parse_object(obj->sha1);
+               obj = deref_tag(obj, NULL, 0);
+               if (!obj)
+                       die("invalid object '%s' given.", name);
+               if (!strcmp(obj->type, commit_type))
+                       obj = &((struct commit *)obj)->tree->object;
+               if (!strcmp(obj->type, tree_type)) {
+                       if (ARRAY_SIZE(ent) <= ents)
+                               die("more than %d trees given: '%s'",
+                                   (int) ARRAY_SIZE(ent), name);
+                       obj->flags |= flags;
+                       ent[ents].item = obj;
+                       ent[ents].name = name;
+                       ents++;
+                       continue;
+               }
+               if (!strcmp(obj->type, blob_type)) {
+                       if (2 <= blobs)
+                               die("more than two blobs given: '%s'", name);
+                       memcpy(blob[blobs].sha1, obj->sha1, 20);
+                       blob[blobs].name = name;
+                       blobs++;
+                       continue;
+
+               }
+               die("unhandled object '%s' given.", name);
+       }
+       if (rev.prune_data) {
+               const char **pathspec = rev.prune_data;
+               while (*pathspec) {
+                       if (!path)
+                               path = *pathspec;
+                       paths++;
+                       pathspec++;
+               }
+       }
+
+       /*
+        * Now, do the arguments look reasonable?
+        */
+       if (!ents) {
+               switch (blobs) {
+               case 0:
+                       return builtin_diff_files(&rev, argc, argv);
+                       break;
+               case 1:
+                       if (paths != 1)
+                               usage(builtin_diff_usage);
+                       return builtin_diff_b_f(&rev, argc, argv, blob, path);
+                       break;
+               case 2:
+                       if (paths)
+                               usage(builtin_diff_usage);
+                       return builtin_diff_blobs(&rev, argc, argv, blob);
+                       break;
+               default:
+                       usage(builtin_diff_usage);
+               }
+       }
+       else if (blobs)
+               usage(builtin_diff_usage);
+       else if (ents == 1)
+               return builtin_diff_index(&rev, argc, argv);
+       else if (ents == 2)
+               return builtin_diff_tree(&rev, argc, argv, ent);
+       else
+               return builtin_diff_combined(&rev, argc, argv, ent, ents);
+       usage(builtin_diff_usage);
+}
diff --git a/builtin-grep.c b/builtin-grep.c
new file mode 100644 (file)
index 0000000..5fac570
--- /dev/null
@@ -0,0 +1,898 @@
+/*
+ * Builtin "git grep"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include "builtin.h"
+#include <regex.h>
+#include <fnmatch.h>
+#include <sys/wait.h>
+
+/*
+ * git grep pathspecs are somewhat different from diff-tree pathspecs;
+ * pathname wildcards are allowed.
+ */
+static int pathspec_matches(const char **paths, const char *name)
+{
+       int namelen, i;
+       if (!paths || !*paths)
+               return 1;
+       namelen = strlen(name);
+       for (i = 0; paths[i]; i++) {
+               const char *match = paths[i];
+               int matchlen = strlen(match);
+               const char *cp, *meta;
+
+               if ((matchlen <= namelen) &&
+                   !strncmp(name, match, matchlen) &&
+                   (match[matchlen-1] == '/' ||
+                    name[matchlen] == '\0' || name[matchlen] == '/'))
+                       return 1;
+               if (!fnmatch(match, name, 0))
+                       return 1;
+               if (name[namelen-1] != '/')
+                       continue;
+
+               /* We are being asked if the directory ("name") is worth
+                * descending into.
+                *
+                * Find the longest leading directory name that does
+                * not have metacharacter in the pathspec; the name
+                * we are looking at must overlap with that directory.
+                */
+               for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
+                       char ch = *cp;
+                       if (ch == '*' || ch == '[' || ch == '?') {
+                               meta = cp;
+                               break;
+                       }
+               }
+               if (!meta)
+                       meta = cp; /* fully literal */
+
+               if (namelen <= meta - match) {
+                       /* Looking at "Documentation/" and
+                        * the pattern says "Documentation/howto/", or
+                        * "Documentation/diff*.txt".  The name we
+                        * have should match prefix.
+                        */
+                       if (!memcmp(match, name, namelen))
+                               return 1;
+                       continue;
+               }
+
+               if (meta - match < namelen) {
+                       /* Looking at "Documentation/howto/" and
+                        * the pattern says "Documentation/h*";
+                        * match up to "Do.../h"; this avoids descending
+                        * into "Documentation/technical/".
+                        */
+                       if (!memcmp(match, name, meta - match))
+                               return 1;
+                       continue;
+               }
+       }
+       return 0;
+}
+
+struct grep_pat {
+       struct grep_pat *next;
+       const char *origin;
+       int no;
+       const char *pattern;
+       regex_t regexp;
+};
+
+struct grep_opt {
+       struct grep_pat *pattern_list;
+       struct grep_pat **pattern_tail;
+       regex_t regexp;
+       unsigned linenum:1;
+       unsigned invert:1;
+       unsigned name_only:1;
+       unsigned unmatch_name_only:1;
+       unsigned count:1;
+       unsigned word_regexp:1;
+       unsigned fixed:1;
+#define GREP_BINARY_DEFAULT    0
+#define GREP_BINARY_NOMATCH    1
+#define GREP_BINARY_TEXT       2
+       unsigned binary:2;
+       int regflags;
+       unsigned pre_context;
+       unsigned post_context;
+};
+
+static void add_pattern(struct grep_opt *opt, const char *pat,
+                       const char *origin, int no)
+{
+       struct grep_pat *p = xcalloc(1, sizeof(*p));
+       p->pattern = pat;
+       p->origin = origin;
+       p->no = no;
+       *opt->pattern_tail = p;
+       opt->pattern_tail = &p->next;
+       p->next = NULL;
+}
+
+static void compile_patterns(struct grep_opt *opt)
+{
+       struct grep_pat *p;
+       for (p = opt->pattern_list; p; p = p->next) {
+               int err = regcomp(&p->regexp, p->pattern, opt->regflags);
+               if (err) {
+                       char errbuf[1024];
+                       char where[1024];
+                       if (p->no)
+                               sprintf(where, "In '%s' at %d, ",
+                                       p->origin, p->no);
+                       else if (p->origin)
+                               sprintf(where, "%s, ", p->origin);
+                       else
+                               where[0] = 0;
+                       regerror(err, &p->regexp, errbuf, 1024);
+                       regfree(&p->regexp);
+                       die("%s'%s': %s", where, p->pattern, errbuf);
+               }
+       }
+}
+
+static char *end_of_line(char *cp, unsigned long *left)
+{
+       unsigned long l = *left;
+       while (l && *cp != '\n') {
+               l--;
+               cp++;
+       }
+       *left = l;
+       return cp;
+}
+
+static int word_char(char ch)
+{
+       return isalnum(ch) || ch == '_';
+}
+
+static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
+                     const char *name, unsigned lno, char sign)
+{
+       printf("%s%c", name, sign);
+       if (opt->linenum)
+               printf("%d%c", lno, sign);
+       printf("%.*s\n", (int)(eol-bol), bol);
+}
+
+/*
+ * NEEDSWORK: share code with diff.c
+ */
+#define FIRST_FEW_BYTES 8000
+static int buffer_is_binary(const char *ptr, unsigned long size)
+{
+       if (FIRST_FEW_BYTES < size)
+               size = FIRST_FEW_BYTES;
+       if (memchr(ptr, 0, size))
+               return 1;
+       return 0;
+}
+
+static int fixmatch(const char *pattern, char *line, regmatch_t *match)
+{
+       char *hit = strstr(line, pattern);
+       if (!hit) {
+               match->rm_so = match->rm_eo = -1;
+               return REG_NOMATCH;
+       }
+       else {
+               match->rm_so = hit - line;
+               match->rm_eo = match->rm_so + strlen(pattern);
+               return 0;
+       }
+}
+
+static int grep_buffer(struct grep_opt *opt, const char *name,
+                      char *buf, unsigned long size)
+{
+       char *bol = buf;
+       unsigned long left = size;
+       unsigned lno = 1;
+       struct pre_context_line {
+               char *bol;
+               char *eol;
+       } *prev = NULL, *pcl;
+       unsigned last_hit = 0;
+       unsigned last_shown = 0;
+       int binary_match_only = 0;
+       const char *hunk_mark = "";
+       unsigned count = 0;
+
+       if (buffer_is_binary(buf, size)) {
+               switch (opt->binary) {
+               case GREP_BINARY_DEFAULT:
+                       binary_match_only = 1;
+                       break;
+               case GREP_BINARY_NOMATCH:
+                       return 0; /* Assume unmatch */
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (opt->pre_context)
+               prev = xcalloc(opt->pre_context, sizeof(*prev));
+       if (opt->pre_context || opt->post_context)
+               hunk_mark = "--\n";
+
+       while (left) {
+               regmatch_t pmatch[10];
+               char *eol, ch;
+               int hit = 0;
+               struct grep_pat *p;
+
+               eol = end_of_line(bol, &left);
+               ch = *eol;
+               *eol = 0;
+
+               for (p = opt->pattern_list; p; p = p->next) {
+                       if (!opt->fixed) {
+                               regex_t *exp = &p->regexp;
+                               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
+                                              pmatch, 0);
+                       }
+                       else {
+                               hit = !fixmatch(p->pattern, bol, pmatch);
+                       }
+
+                       if (hit && opt->word_regexp) {
+                               /* Match beginning must be either
+                                * beginning of the line, or at word
+                                * boundary (i.e. the last char must
+                                * not be alnum or underscore).
+                                */
+                               if ((pmatch[0].rm_so < 0) ||
+                                   (eol - bol) <= pmatch[0].rm_so ||
+                                   (pmatch[0].rm_eo < 0) ||
+                                   (eol - bol) < pmatch[0].rm_eo)
+                                       die("regexp returned nonsense");
+                               if (pmatch[0].rm_so != 0 &&
+                                   word_char(bol[pmatch[0].rm_so-1]))
+                                       hit = 0;
+                               if (pmatch[0].rm_eo != (eol-bol) &&
+                                   word_char(bol[pmatch[0].rm_eo]))
+                                       hit = 0;
+                       }
+                       if (hit)
+                               break;
+               }
+               /* "grep -v -e foo -e bla" should list lines
+                * that do not have either, so inversion should
+                * be done outside.
+                */
+               if (opt->invert)
+                       hit = !hit;
+               if (opt->unmatch_name_only) {
+                       if (hit)
+                               return 0;
+                       goto next_line;
+               }
+               if (hit) {
+                       count++;
+                       if (binary_match_only) {
+                               printf("Binary file %s matches\n", name);
+                               return 1;
+                       }
+                       if (opt->name_only) {
+                               printf("%s\n", name);
+                               return 1;
+                       }
+                       /* Hit at this line.  If we haven't shown the
+                        * pre-context lines, we would need to show them.
+                        * When asked to do "count", this still show
+                        * the context which is nonsense, but the user
+                        * deserves to get that ;-).
+                        */
+                       if (opt->pre_context) {
+                               unsigned from;
+                               if (opt->pre_context < lno)
+                                       from = lno - opt->pre_context;
+                               else
+                                       from = 1;
+                               if (from <= last_shown)
+                                       from = last_shown + 1;
+                               if (last_shown && from != last_shown + 1)
+                                       printf(hunk_mark);
+                               while (from < lno) {
+                                       pcl = &prev[lno-from-1];
+                                       show_line(opt, pcl->bol, pcl->eol,
+                                                 name, from, '-');
+                                       from++;
+                               }
+                               last_shown = lno-1;
+                       }
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       if (!opt->count)
+                               show_line(opt, bol, eol, name, lno, ':');
+                       last_shown = last_hit = lno;
+               }
+               else if (last_hit &&
+                        lno <= last_hit + opt->post_context) {
+                       /* If the last hit is within the post context,
+                        * we need to show this line.
+                        */
+                       if (last_shown && lno != last_shown + 1)
+                               printf(hunk_mark);
+                       show_line(opt, bol, eol, name, lno, '-');
+                       last_shown = lno;
+               }
+               if (opt->pre_context) {
+                       memmove(prev+1, prev,
+                               (opt->pre_context-1) * sizeof(*prev));
+                       prev->bol = bol;
+                       prev->eol = eol;
+               }
+
+       next_line:
+               *eol = ch;
+               bol = eol + 1;
+               if (!left)
+                       break;
+               left--;
+               lno++;
+       }
+
+       if (opt->unmatch_name_only) {
+               /* We did not see any hit, so we want to show this */
+               printf("%s\n", name);
+               return 1;
+       }
+
+       /* NEEDSWORK:
+        * The real "grep -c foo *.c" gives many "bar.c:0" lines,
+        * which feels mostly useless but sometimes useful.  Maybe
+        * make it another option?  For now suppress them.
+        */
+       if (opt->count && count)
+               printf("%s:%u\n", name, count);
+       return !!last_hit;
+}
+
+static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name)
+{
+       unsigned long size;
+       char *data;
+       char type[20];
+       int hit;
+       data = read_sha1_file(sha1, type, &size);
+       if (!data) {
+               error("'%s': unable to read %s", name, sha1_to_hex(sha1));
+               return 0;
+       }
+       hit = grep_buffer(opt, name, data, size);
+       free(data);
+       return hit;
+}
+
+static int grep_file(struct grep_opt *opt, const char *filename)
+{
+       struct stat st;
+       int i;
+       char *data;
+       if (lstat(filename, &st) < 0) {
+       err_ret:
+               if (errno != ENOENT)
+                       error("'%s': %s", filename, strerror(errno));
+               return 0;
+       }
+       if (!st.st_size)
+               return 0; /* empty file -- no grep hit */
+       if (!S_ISREG(st.st_mode))
+               return 0;
+       i = open(filename, O_RDONLY);
+       if (i < 0)
+               goto err_ret;
+       data = xmalloc(st.st_size + 1);
+       if (st.st_size != xread(i, data, st.st_size)) {
+               error("'%s': short read %s", filename, strerror(errno));
+               close(i);
+               free(data);
+               return 0;
+       }
+       close(i);
+       i = grep_buffer(opt, filename, data, st.st_size);
+       free(data);
+       return i;
+}
+
+static int exec_grep(int argc, const char **argv)
+{
+       pid_t pid;
+       int status;
+
+       argv[argc] = NULL;
+       pid = fork();
+       if (pid < 0)
+               return pid;
+       if (!pid) {
+               execvp("grep", (char **) argv);
+               exit(255);
+       }
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+       if (WIFEXITED(status)) {
+               if (!WEXITSTATUS(status))
+                       return 1;
+               return 0;
+       }
+       return -1;
+}
+
+#define MAXARGS 1000
+#define ARGBUF 4096
+#define push_arg(a) do { \
+       if (nr < MAXARGS) argv[nr++] = (a); \
+       else die("maximum number of args exceeded"); \
+       } while (0)
+
+static int external_grep(struct grep_opt *opt, const char **paths, int cached)
+{
+       int i, nr, argc, hit, len;
+       const char *argv[MAXARGS+1];
+       char randarg[ARGBUF];
+       char *argptr = randarg;
+       struct grep_pat *p;
+
+       len = nr = 0;
+       push_arg("grep");
+       if (opt->fixed)
+               push_arg("-F");
+       if (opt->linenum)
+               push_arg("-n");
+       if (opt->regflags & REG_EXTENDED)
+               push_arg("-E");
+       if (opt->regflags & REG_ICASE)
+               push_arg("-i");
+       if (opt->word_regexp)
+               push_arg("-w");
+       if (opt->name_only)
+               push_arg("-l");
+       if (opt->unmatch_name_only)
+               push_arg("-L");
+       if (opt->count)
+               push_arg("-c");
+       if (opt->post_context || opt->pre_context) {
+               if (opt->post_context != opt->pre_context) {
+                       if (opt->pre_context) {
+                               push_arg("-B");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->pre_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+                       if (opt->post_context) {
+                               push_arg("-A");
+                               len += snprintf(argptr, sizeof(randarg)-len,
+                                               "%u", opt->post_context);
+                               if (sizeof(randarg) <= len)
+                                       die("maximum length of args exceeded");
+                               push_arg(argptr);
+                               argptr += len;
+                       }
+               }
+               else {
+                       push_arg("-C");
+                       len += snprintf(argptr, sizeof(randarg)-len,
+                                       "%u", opt->post_context);
+                       if (sizeof(randarg) <= len)
+                               die("maximum length of args exceeded");
+                       push_arg(argptr);
+                       argptr += len;
+               }
+       }
+       for (p = opt->pattern_list; p; p = p->next) {
+               push_arg("-e");
+               push_arg(p->pattern);
+       }
+
+       /*
+        * To make sure we get the header printed out when we want it,
+        * add /dev/null to the paths to grep.  This is unnecessary
+        * (and wrong) with "-l" or "-L", which always print out the
+        * name anyway.
+        *
+        * GNU grep has "-H", but this is portable.
+        */
+       if (!opt->name_only && !opt->unmatch_name_only)
+               push_arg("/dev/null");
+
+       hit = 0;
+       argc = nr;
+       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)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               name = ce->name;
+               if (name[0] == '-') {
+                       int len = ce_namelen(ce);
+                       name = xmalloc(len + 3);
+                       memcpy(name, "./", 2);
+                       memcpy(name + 2, ce->name, len + 1);
+               }
+               argv[argc++] = name;
+               if (argc < MAXARGS)
+                       continue;
+               hit += exec_grep(argc, argv);
+               argc = nr;
+       }
+       if (argc > nr)
+               hit += exec_grep(argc, argv);
+       return 0;
+}
+
+static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
+{
+       int hit = 0;
+       int nr;
+       read_cache();
+
+#ifdef __unix__
+       /*
+        * Use the external "grep" command for the case where
+        * we grep through the checked-out files. It tends to
+        * be a lot more optimized
+        */
+       if (!cached) {
+               hit = external_grep(opt, paths, cached);
+               if (hit >= 0)
+                       return hit;
+       }
+#endif
+
+       for (nr = 0; nr < active_nr; nr++) {
+               struct cache_entry *ce = active_cache[nr];
+               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+                       continue;
+               if (!pathspec_matches(paths, ce->name))
+                       continue;
+               if (cached)
+                       hit |= grep_sha1(opt, ce->sha1, ce->name);
+               else
+                       hit |= grep_file(opt, ce->name);
+       }
+       return hit;
+}
+
+static int grep_tree(struct grep_opt *opt, const char **paths,
+                    struct tree_desc *tree,
+                    const char *tree_name, const char *base)
+{
+       int len;
+       int hit = 0;
+       struct name_entry entry;
+       char *down;
+       char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100);
+
+       if (tree_name[0]) {
+               int offset = sprintf(path_buf, "%s:", tree_name);
+               down = path_buf + offset;
+               strcat(down, base);
+       }
+       else {
+               down = path_buf;
+               strcpy(down, base);
+       }
+       len = strlen(path_buf);
+
+       while (tree_entry(tree, &entry)) {
+               strcpy(path_buf + len, entry.path);
+
+               if (S_ISDIR(entry.mode))
+                       /* Match "abc/" against pathspec to
+                        * decide if we want to descend into "abc"
+                        * directory.
+                        */
+                       strcpy(path_buf + len + entry.pathlen, "/");
+
+               if (!pathspec_matches(paths, down))
+                       ;
+               else if (S_ISREG(entry.mode))
+                       hit |= grep_sha1(opt, entry.sha1, path_buf);
+               else if (S_ISDIR(entry.mode)) {
+                       char type[20];
+                       struct tree_desc sub;
+                       void *data;
+                       data = read_sha1_file(entry.sha1, type, &sub.size);
+                       if (!data)
+                               die("unable to read tree (%s)",
+                                   sha1_to_hex(entry.sha1));
+                       sub.buf = data;
+                       hit |= grep_tree(opt, paths, &sub, tree_name, down);
+                       free(data);
+               }
+       }
+       return hit;
+}
+
+static int grep_object(struct grep_opt *opt, const char **paths,
+                      struct object *obj, const char *name)
+{
+       if (!strcmp(obj->type, blob_type))
+               return grep_sha1(opt, obj->sha1, name);
+       if (!strcmp(obj->type, commit_type) ||
+           !strcmp(obj->type, tree_type)) {
+               struct tree_desc tree;
+               void *data;
+               int hit;
+               data = read_object_with_reference(obj->sha1, tree_type,
+                                                 &tree.size, NULL);
+               if (!data)
+                       die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
+               tree.buf = data;
+               hit = grep_tree(opt, paths, &tree, name, "");
+               free(data);
+               return hit;
+       }
+       die("unable to grep from object of type %s", obj->type);
+}
+
+static const char builtin_grep_usage[] =
+"git-grep <option>* <rev>* [-e] <pattern> [<path>...]";
+
+int cmd_grep(int argc, const char **argv, char **envp)
+{
+       int hit = 0;
+       int cached = 0;
+       int seen_dashdash = 0;
+       struct grep_opt opt;
+       struct object_list *list, **tail, *object_list = NULL;
+       const char *prefix = setup_git_directory();
+       const char **paths = NULL;
+       int i;
+
+       memset(&opt, 0, sizeof(opt));
+       opt.pattern_tail = &opt.pattern_list;
+       opt.regflags = REG_NEWLINE;
+
+       /*
+        * If there is no -- then the paths must exist in the working
+        * tree.  If there is no explicit pattern specified with -e or
+        * -f, we take the first unrecognized non option to be the
+        * pattern, but then what follows it must be zero or more
+        * valid refs up to the -- (if exists), and then existing
+        * paths.  If there is an explicit pattern, then the first
+        * unrecocnized non option is the beginning of the refs list
+        * that continues up to the -- (if exists), and then paths.
+        */
+
+       tail = &object_list;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               argc--; argv++;
+               if (!strcmp("--cached", arg)) {
+                       cached = 1;
+                       continue;
+               }
+               if (!strcmp("-a", arg) ||
+                   !strcmp("--text", arg)) {
+                       opt.binary = GREP_BINARY_TEXT;
+                       continue;
+               }
+               if (!strcmp("-i", arg) ||
+                   !strcmp("--ignore-case", arg)) {
+                       opt.regflags |= REG_ICASE;
+                       continue;
+               }
+               if (!strcmp("-I", arg)) {
+                       opt.binary = GREP_BINARY_NOMATCH;
+                       continue;
+               }
+               if (!strcmp("-v", arg) ||
+                   !strcmp("--invert-match", arg)) {
+                       opt.invert = 1;
+                       continue;
+               }
+               if (!strcmp("-E", arg) ||
+                   !strcmp("--extended-regexp", arg)) {
+                       opt.regflags |= REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-F", arg) ||
+                   !strcmp("--fixed-strings", arg)) {
+                       opt.fixed = 1;
+                       continue;
+               }
+               if (!strcmp("-G", arg) ||
+                   !strcmp("--basic-regexp", arg)) {
+                       opt.regflags &= ~REG_EXTENDED;
+                       continue;
+               }
+               if (!strcmp("-n", arg)) {
+                       opt.linenum = 1;
+                       continue;
+               }
+               if (!strcmp("-H", arg)) {
+                       /* We always show the pathname, so this
+                        * is a noop.
+                        */
+                       continue;
+               }
+               if (!strcmp("-l", arg) ||
+                   !strcmp("--files-with-matches", arg)) {
+                       opt.name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-L", arg) ||
+                   !strcmp("--files-without-match", arg)) {
+                       opt.unmatch_name_only = 1;
+                       continue;
+               }
+               if (!strcmp("-c", arg) ||
+                   !strcmp("--count", arg)) {
+                       opt.count = 1;
+                       continue;
+               }
+               if (!strcmp("-w", arg) ||
+                   !strcmp("--word-regexp", arg)) {
+                       opt.word_regexp = 1;
+                       continue;
+               }
+               if (!strncmp("-A", arg, 2) ||
+                   !strncmp("-B", arg, 2) ||
+                   !strncmp("-C", arg, 2) ||
+                   (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) {
+                       unsigned num;
+                       const char *scan;
+                       switch (arg[1]) {
+                       case 'A': case 'B': case 'C':
+                               if (!arg[2]) {
+                                       if (argc <= 1)
+                                               usage(builtin_grep_usage);
+                                       scan = *++argv;
+                                       argc--;
+                               }
+                               else
+                                       scan = arg + 2;
+                               break;
+                       default:
+                               scan = arg + 1;
+                               break;
+                       }
+                       if (sscanf(scan, "%u", &num) != 1)
+                               usage(builtin_grep_usage);
+                       switch (arg[1]) {
+                       case 'A':
+                               opt.post_context = num;
+                               break;
+                       default:
+                       case 'C':
+                               opt.post_context = num;
+                       case 'B':
+                               opt.pre_context = num;
+                               break;
+                       }
+                       continue;
+               }
+               if (!strcmp("-f", arg)) {
+                       FILE *patterns;
+                       int lno = 0;
+                       char buf[1024];
+                       if (argc <= 1)
+                               usage(builtin_grep_usage);
+                       patterns = fopen(argv[1], "r");
+                       if (!patterns)
+                               die("'%s': %s", argv[1], strerror(errno));
+                       while (fgets(buf, sizeof(buf), patterns)) {
+                               int len = strlen(buf);
+                               if (buf[len-1] == '\n')
+                                       buf[len-1] = 0;
+                               /* ignore empty line like grep does */
+                               if (!buf[0])
+                                       continue;
+                               add_pattern(&opt, strdup(buf), argv[1], ++lno);
+                       }
+                       fclose(patterns);
+                       argv++;
+                       argc--;
+                       continue;
+               }
+               if (!strcmp("-e", arg)) {
+                       if (1 < argc) {
+                               add_pattern(&opt, argv[1], "-e option", 0);
+                               argv++;
+                               argc--;
+                               continue;
+                       }
+                       usage(builtin_grep_usage);
+               }
+               if (!strcmp("--", arg))
+                       break;
+               if (*arg == '-')
+                       usage(builtin_grep_usage);
+
+               /* First unrecognized non-option token */
+               if (!opt.pattern_list) {
+                       add_pattern(&opt, arg, "command line", 0);
+                       break;
+               }
+               else {
+                       /* We are looking at the first path or rev;
+                        * it is found at argv[1] after leaving the
+                        * loop.
+                        */
+                       argc++; argv--;
+                       break;
+               }
+       }
+
+       if (!opt.pattern_list)
+               die("no pattern given.");
+       if ((opt.regflags != REG_NEWLINE) && opt.fixed)
+               die("cannot mix --fixed-strings and regexp");
+       if (!opt.fixed)
+               compile_patterns(&opt);
+
+       /* Check revs and then paths */
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               /* Is it a rev? */
+               if (!get_sha1(arg, sha1)) {
+                       struct object *object = parse_object(sha1);
+                       struct object_list *elem;
+                       if (!object)
+                               die("bad object %s", arg);
+                       elem = object_list_insert(object, tail);
+                       elem->name = arg;
+                       tail = &elem->next;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       seen_dashdash = 1;
+               }
+               break;
+       }
+
+       /* The rest are paths */
+       if (!seen_dashdash) {
+               int j;
+               for (j = i; j < argc; j++)
+                       verify_filename(prefix, argv[j]);
+       }
+
+       if (i < argc)
+               paths = get_pathspec(prefix, argv + i);
+       else if (prefix) {
+               paths = xcalloc(2, sizeof(const char *));
+               paths[0] = prefix;
+               paths[1] = NULL;
+       }
+
+       if (!object_list)
+               return !grep_cache(&opt, paths, cached);
+
+       if (cached)
+               die("both --cached and trees are given.");
+
+       for (list = object_list; list; list = list->next) {
+               struct object *real_obj;
+               real_obj = deref_tag(list->item, NULL, 0);
+               if (grep_object(&opt, paths, real_obj, list->name))
+                       hit = 1;
+       }
+       return !hit;
+}
diff --git a/builtin-help.c b/builtin-help.c
new file mode 100644 (file)
index 0000000..7470faa
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * builtin-help.c
+ *
+ * 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"
+
+static const char git_usage[] =
+       "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+
+/* most gui terms set COLUMNS (although some don't export it) */
+static int term_columns(void)
+{
+       char *col_string = getenv("COLUMNS");
+       int n_cols = 0;
+
+       if (col_string && (n_cols = atoi(col_string)) > 0)
+               return n_cols;
+
+#ifdef TIOCGWINSZ
+       {
+               struct winsize ws;
+               if (!ioctl(1, TIOCGWINSZ, &ws)) {
+                       if (ws.ws_col)
+                               return ws.ws_col;
+               }
+       }
+#endif
+
+       return 80;
+}
+
+static void oom(void)
+{
+       fprintf(stderr, "git: out of memory\n");
+       exit(1);
+}
+
+static inline void mput_char(char c, unsigned int num)
+{
+       while(num--)
+               putchar(c);
+}
+
+static struct cmdname {
+       size_t len;
+       char name[1];
+} **cmdname;
+static int cmdname_alloc, cmdname_cnt;
+
+static void add_cmdname(const char *name, int len)
+{
+       struct cmdname *ent;
+       if (cmdname_alloc <= cmdname_cnt) {
+               cmdname_alloc = cmdname_alloc + 200;
+               cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
+               if (!cmdname)
+                       oom();
+       }
+       ent = malloc(sizeof(*ent) + len);
+       if (!ent)
+               oom();
+       ent->len = len;
+       memcpy(ent->name, name, len);
+       ent->name[len] = 0;
+       cmdname[cmdname_cnt++] = ent;
+}
+
+static int cmdname_compare(const void *a_, const void *b_)
+{
+       struct cmdname *a = *(struct cmdname **)a_;
+       struct cmdname *b = *(struct cmdname **)b_;
+       return strcmp(a->name, b->name);
+}
+
+static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+{
+       int cols = 1, rows;
+       int space = longest + 1; /* min 1 SP between words */
+       int max_cols = term_columns() - 1; /* don't print *on* the edge */
+       int i, j;
+
+       if (space < max_cols)
+               cols = max_cols / space;
+       rows = (cmdname_cnt + cols - 1) / cols;
+
+       qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+
+       for (i = 0; i < rows; i++) {
+               printf("  ");
+
+               for (j = 0; j < cols; j++) {
+                       int n = j * rows + i;
+                       int size = space;
+                       if (n >= cmdname_cnt)
+                               break;
+                       if (j == cols-1 || n + rows >= cmdname_cnt)
+                               size = 1;
+                       printf("%-*s", size, cmdname[n]->name);
+               }
+               putchar('\n');
+       }
+}
+
+static void list_commands(const char *exec_path, const char *pattern)
+{
+       unsigned int longest = 0;
+       char path[PATH_MAX];
+       int dirlen;
+       DIR *dir = opendir(exec_path);
+       struct dirent *de;
+
+       if (!dir) {
+               fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
+               exit(1);
+       }
+
+       dirlen = strlen(exec_path);
+       if (PATH_MAX - 20 < dirlen) {
+               fprintf(stderr, "git: insanely long exec-path '%s'\n",
+                       exec_path);
+               exit(1);
+       }
+
+       memcpy(path, exec_path, dirlen);
+       path[dirlen++] = '/';
+
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st;
+               int entlen;
+
+               if (strncmp(de->d_name, "git-", 4))
+                       continue;
+               strcpy(path+dirlen, de->d_name);
+               if (stat(path, &st) || /* stat, not lstat */
+                   !S_ISREG(st.st_mode) ||
+                   !(st.st_mode & S_IXUSR))
+                       continue;
+
+               entlen = strlen(de->d_name);
+               if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
+                       entlen -= 4;
+
+               if (longest < entlen)
+                       longest = entlen;
+
+               add_cmdname(de->d_name + 4, entlen-4);
+       }
+       closedir(dir);
+
+       printf("git commands available in '%s'\n", exec_path);
+       printf("----------------------------");
+       mput_char('-', strlen(exec_path));
+       putchar('\n');
+       pretty_print_string_list(cmdname, longest - 4);
+       putchar('\n');
+}
+
+static void list_common_cmds_help(void)
+{
+       int i, longest = 0;
+
+       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               if (longest < strlen(common_cmds[i].name))
+                       longest = strlen(common_cmds[i].name);
+       }
+
+       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);
+               puts(common_cmds[i].help);
+       }
+       puts("(use 'git help -a' to get a list of all installed git commands)");
+}
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+{
+       if (fmt) {
+               va_list ap;
+
+               va_start(ap, fmt);
+               printf("git: ");
+               vprintf(fmt, ap);
+               va_end(ap);
+               putchar('\n');
+       }
+       else
+               puts(git_usage);
+
+       if (exec_path) {
+               putchar('\n');
+               if (show_all)
+                       list_commands(exec_path, "git-*");
+               else
+                       list_common_cmds_help();
+        }
+
+       exit(1);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+       const char *page;
+
+       if (!strncmp(git_cmd, "git", 3))
+               page = git_cmd;
+       else {
+               int page_len = strlen(git_cmd) + 4;
+               char *p = malloc(page_len + 1);
+               strcpy(p, "git-");
+               strcpy(p + 4, git_cmd);
+               p[page_len] = 0;
+               page = p;
+       }
+
+       execlp("man", "man", page, NULL);
+}
+
+int cmd_version(int argc, const char **argv, char **envp)
+{
+       printf("git version %s\n", git_version_string);
+       return 0;
+}
+
+int cmd_help(int argc, const char **argv, char **envp)
+{
+       const char *help_cmd = argv[1];
+       if (!help_cmd)
+               cmd_usage(0, git_exec_path(), NULL);
+       else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
+               cmd_usage(1, git_exec_path(), NULL);
+       else
+               show_man_page(help_cmd);
+       return 0;
+}
+
+
diff --git a/builtin-init-db.c b/builtin-init-db.c
new file mode 100644 (file)
index 0000000..2a1384c
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "builtin.h"
+
+#ifndef DEFAULT_GIT_TEMPLATE_DIR
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
+#endif
+
+static void safe_create_dir(const char *dir, int share)
+{
+       if (mkdir(dir, 0777) < 0) {
+               if (errno != EEXIST) {
+                       perror(dir);
+                       exit(1);
+               }
+       }
+       else if (share && adjust_shared_perm(dir))
+               die("Could not make %s writable by group\n", dir);
+}
+
+static int copy_file(const char *dst, const char *src, int mode)
+{
+       int fdi, fdo, status;
+
+       mode = (mode & 0111) ? 0777 : 0666;
+       if ((fdi = open(src, O_RDONLY)) < 0)
+               return fdi;
+       if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
+               close(fdi);
+               return fdo;
+       }
+       status = copy_fd(fdi, fdo);
+       close(fdo);
+
+       if (!status && adjust_shared_perm(dst))
+               return -1;
+
+       return status;
+}
+
+static void copy_templates_1(char *path, int baselen,
+                            char *template, int template_baselen,
+                            DIR *dir)
+{
+       struct dirent *de;
+
+       /* 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
+        * 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.
+        */
+       safe_create_dir(path, 1);
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st_git, st_template;
+               int namelen;
+               int exists = 0;
+
+               if (de->d_name[0] == '.')
+                       continue;
+               namelen = strlen(de->d_name);
+               if ((PATH_MAX <= baselen + namelen) ||
+                   (PATH_MAX <= template_baselen + namelen))
+                       die("insanely long template name %s", de->d_name);
+               memcpy(path + baselen, de->d_name, namelen+1);
+               memcpy(template + template_baselen, de->d_name, namelen+1);
+               if (lstat(path, &st_git)) {
+                       if (errno != ENOENT)
+                               die("cannot stat %s", path);
+               }
+               else
+                       exists = 1;
+
+               if (lstat(template, &st_template))
+                       die("cannot stat template %s", template);
+
+               if (S_ISDIR(st_template.st_mode)) {
+                       DIR *subdir = opendir(template);
+                       int baselen_sub = baselen + namelen;
+                       int template_baselen_sub = template_baselen + namelen;
+                       if (!subdir)
+                               die("cannot opendir %s", template);
+                       path[baselen_sub++] =
+                               template[template_baselen_sub++] = '/';
+                       path[baselen_sub] =
+                               template[template_baselen_sub] = 0;
+                       copy_templates_1(path, baselen_sub,
+                                        template, template_baselen_sub,
+                                        subdir);
+                       closedir(subdir);
+               }
+               else if (exists)
+                       continue;
+               else if (S_ISLNK(st_template.st_mode)) {
+                       char lnk[256];
+                       int len;
+                       len = readlink(template, lnk, sizeof(lnk));
+                       if (len < 0)
+                               die("cannot readlink %s", template);
+                       if (sizeof(lnk) <= len)
+                               die("insanely long symlink %s", template);
+                       lnk[len] = 0;
+                       if (symlink(lnk, path))
+                               die("cannot symlink %s %s", lnk, path);
+               }
+               else if (S_ISREG(st_template.st_mode)) {
+                       if (copy_file(path, template, st_template.st_mode))
+                               die("cannot copy %s to %s", template, path);
+               }
+               else
+                       error("ignoring template %s", template);
+       }
+}
+
+static void copy_templates(const char *git_dir, int len, const char *template_dir)
+{
+       char path[PATH_MAX];
+       char template_path[PATH_MAX];
+       int template_len;
+       DIR *dir;
+
+       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] != '/') {
+               template_path[template_len++] = '/';
+               template_path[template_len] = 0;
+       }
+       dir = opendir(template_path);
+       if (!dir) {
+               fprintf(stderr, "warning: templates not found %s\n",
+                       template_dir);
+               return;
+       }
+
+       /* Make sure that template is from the correct vintage */
+       strcpy(template_path + template_len, "config");
+       repository_format_version = 0;
+       git_config_from_file(check_repository_format_version,
+                            template_path);
+       template_path[template_len] = 0;
+
+       if (repository_format_version &&
+           repository_format_version != GIT_REPO_VERSION) {
+               fprintf(stderr, "warning: not copying templates of "
+                       "a wrong format version %d from '%s'\n",
+                       repository_format_version,
+                       template_dir);
+               closedir(dir);
+               return;
+       }
+
+       memcpy(path, git_dir, len);
+       path[len] = 0;
+       copy_templates_1(path, len,
+                        template_path, template_len,
+                        dir);
+       closedir(dir);
+}
+
+static void 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];
+
+       if (len > sizeof(path)-50)
+               die("insane git directory %s", git_dir);
+       memcpy(path, git_dir, len);
+
+       if (len && path[len-1] != '/')
+               path[len++] = '/';
+
+       /*
+        * Create .git/refs/{heads,tags}
+        */
+       strcpy(path + len, "refs");
+       safe_create_dir(path, 1);
+       strcpy(path + len, "refs/heads");
+       safe_create_dir(path, 1);
+       strcpy(path + len, "refs/tags");
+       safe_create_dir(path, 1);
+
+       /* First copy the templates -- we might have the default
+        * config file there, in which case we would want to read
+        * from it after installing.
+        */
+       path[len] = 0;
+       copy_templates(path, len, template_path);
+
+       git_config(git_default_config);
+
+       /*
+        * Create the default symlink from ".git/HEAD" to the "master"
+        * branch, if it does not exist yet.
+        */
+       strcpy(path + len, "HEAD");
+       if (read_ref(path, sha1) < 0) {
+               if (create_symref(path, "refs/heads/master") < 0)
+                       exit(1);
+       }
+
+       /* This forces creation of new config file */
+       sprintf(repo_version_string, "%d", GIT_REPO_VERSION);
+       git_config_set("core.repositoryformatversion", repo_version_string);
+
+       path[len] = 0;
+       strcpy(path + len, "config");
+
+       /* Check filemode trustability */
+       if (!lstat(path, &st1)) {
+               struct stat st2;
+               int 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");
+       }
+}
+
+static const char init_db_usage[] =
+"git-init-db [--template=<template-directory>] [--shared]";
+
+/*
+ * If you want to, you can share the DB area with any number of branches.
+ * That has advantages: you can save space by sharing all the SHA1 objects.
+ * On the other hand, it might just make lookup slower and messier. You
+ * be the judge.  The default case is to have one DB per managed directory.
+ */
+int cmd_init_db(int argc, const char **argv, char **envp)
+{
+       const char *git_dir;
+       const char *sha1_dir;
+       const char *template_dir = NULL;
+       char *path;
+       int len, i;
+
+       for (i = 1; i < argc; i++, argv++) {
+               const char *arg = argv[1];
+               if (!strncmp(arg, "--template=", 11))
+                       template_dir = arg+11;
+               else if (!strcmp(arg, "--shared"))
+                       shared_repository = 1;
+               else
+                       die(init_db_usage);
+       }
+
+       /*
+        * Set up the default .git directory contents
+        */
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
+       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.
+        * Note that a newly created repository does not have
+        * config file, so this will not fail.  What we are catching
+        * is an attempt to reinitialize new repository with an old tool.
+        */
+       check_repository_format();
+
+       create_default_files(git_dir, template_dir);
+
+       /*
+        * And set up the object store.
+        */
+       sha1_dir = get_object_directory();
+       len = strlen(sha1_dir);
+       path = xmalloc(len + 40);
+       memcpy(path, sha1_dir, len);
+
+       safe_create_dir(sha1_dir, 1);
+       strcpy(path+len, "/pack");
+       safe_create_dir(path, 1);
+       strcpy(path+len, "/info");
+       safe_create_dir(path, 1);
+
+       if (shared_repository)
+               git_config_set("core.sharedRepository", "true");
+
+       return 0;
+}
diff --git a/builtin-log.c b/builtin-log.c
new file mode 100644 (file)
index 0000000..29a8851
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * Builtin "git log" and related commands (show, whatchanged)
+ *
+ * (C) Copyright 2006 Linus Torvalds
+ *              2006 Junio Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+/* this is in builtin-diff.c */
+void add_head(struct rev_info *revs);
+
+static int cmd_log_wc(int argc, const char **argv, char **envp,
+                     struct rev_info *rev)
+{
+       struct commit *commit;
+
+       rev->abbrev = DEFAULT_ABBREV;
+       rev->commit_format = CMIT_FMT_DEFAULT;
+       rev->verbose_header = 1;
+       argc = setup_revisions(argc, argv, rev, "HEAD");
+       if (rev->always_show_header) {
+               if (rev->diffopt.pickaxe || rev->diffopt.filter) {
+                       rev->always_show_header = 0;
+                       if (rev->diffopt.output_format == DIFF_FORMAT_RAW)
+                               rev->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
+               }
+       }
+
+       if (argc > 1)
+               die("unrecognized argument: %s", argv[1]);
+
+       prepare_revision_walk(rev);
+       setup_pager();
+       while ((commit = get_revision(rev)) != NULL) {
+               log_tree_commit(rev, commit);
+               free(commit->buffer);
+               commit->buffer = NULL;
+       }
+       return 0;
+}
+
+int cmd_whatchanged(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.diff = 1;
+       rev.diffopt.recursive = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_show(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.diff = 1;
+       rev.diffopt.recursive = 1;
+       rev.combine_merges = 1;
+       rev.dense_combined_merges = 1;
+       rev.always_show_header = 1;
+       rev.ignore_merges = 0;
+       rev.no_walk = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_log(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.always_show_header = 1;
+       rev.diffopt.recursive = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+static int istitlechar(char c)
+{
+       return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+               (c >= '0' && c <= '9') || c == '.' || c == '_';
+}
+
+static char *extra_headers = NULL;
+static int extra_headers_size = 0;
+
+static int git_format_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "format.headers")) {
+               int len = strlen(value);
+               extra_headers_size += len + 1;
+               extra_headers = realloc(extra_headers, extra_headers_size);
+               extra_headers[extra_headers_size - len - 1] = 0;
+               strcat(extra_headers, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+
+static FILE *realstdout = NULL;
+static const char *output_directory = NULL;
+
+static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
+{
+       char filename[1024];
+       char *sol;
+       int len = 0;
+
+       if (output_directory) {
+               strncpy(filename, output_directory, 1010);
+               len = strlen(filename);
+               if (filename[len - 1] != '/')
+                       filename[len++] = '/';
+       }
+
+       sprintf(filename + len, "%04d", nr);
+       len = strlen(filename);
+
+       sol = strstr(commit->buffer, "\n\n");
+       if (sol) {
+               int j, space = 1;
+
+               sol += 2;
+               /* strip [PATCH] or [PATCH blabla] */
+               if (!keep_subject && !strncmp(sol, "[PATCH", 6)) {
+                       char *eos = strchr(sol + 6, ']');
+                       if (eos) {
+                               while (isspace(*eos))
+                                       eos++;
+                               sol = eos;
+                       }
+               }
+
+               for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) {
+                       if (istitlechar(sol[j])) {
+                               if (space) {
+                                       filename[len++] = '-';
+                                       space = 0;
+                               }
+                               filename[len++] = sol[j];
+                               if (sol[j] == '.')
+                                       while (sol[j + 1] == '.')
+                                               j++;
+                       } else
+                               space = 1;
+               }
+               while (filename[len - 1] == '.' || filename[len - 1] == '-')
+                       len--;
+       }
+       strcpy(filename + len, ".txt");
+       fprintf(realstdout, "%s\n", filename);
+       freopen(filename, "w", stdout);
+}
+
+int cmd_format_patch(int argc, const char **argv, char **envp)
+{
+       struct commit *commit;
+       struct commit **list = NULL;
+       struct rev_info rev;
+       int nr = 0, total, i, j;
+       int use_stdout = 0;
+       int numbered = 0;
+       int start_number = -1;
+       int keep_subject = 0;
+       char *add_signoff = NULL;
+
+       init_revisions(&rev);
+       rev.commit_format = CMIT_FMT_EMAIL;
+       rev.verbose_header = 1;
+       rev.diff = 1;
+       rev.diffopt.with_raw = 0;
+       rev.diffopt.with_stat = 1;
+       rev.combine_merges = 0;
+       rev.ignore_merges = 1;
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+
+       git_config(git_format_config);
+       rev.extra_headers = extra_headers;
+
+       /*
+        * Parse the arguments before setup_revisions(), or something
+        * like "git fmt-patch -o a123 HEAD^.." may fail; a123 is
+        * possibly a valid SHA1.
+        */
+       for (i = 1, j = 1; i < argc; i++) {
+               if (!strcmp(argv[i], "--stdout"))
+                       use_stdout = 1;
+               else if (!strcmp(argv[i], "-n") ||
+                               !strcmp(argv[i], "--numbered"))
+                       numbered = 1;
+               else if (!strncmp(argv[i], "--start-number=", 15))
+                       start_number = strtol(argv[i] + 15, NULL, 10);
+               else if (!strcmp(argv[i], "--start-number")) {
+                       i++;
+                       if (i == argc)
+                               die("Need a number for --start-number");
+                       start_number = strtol(argv[i], NULL, 10);
+               }
+               else if (!strcmp(argv[i], "-k") ||
+                               !strcmp(argv[i], "--keep-subject")) {
+                       keep_subject = 1;
+                       rev.total = -1;
+               }
+               else if (!strcmp(argv[i], "--output-directory") ||
+                        !strcmp(argv[i], "-o")) {
+                       i++;
+                       if (argc <= i)
+                               die("Which directory?");
+                       if (output_directory)
+                               die("Two output directories?");
+                       output_directory = argv[i];
+               }
+               else if (!strcmp(argv[i], "--signoff") ||
+                        !strcmp(argv[i], "-s")) {
+                       const char *committer = git_committer_info(1);
+                       const char *endpos = strchr(committer, '>');
+                       if (!endpos)
+                               die("bogos committer info %s\n", committer);
+                       add_signoff = xmalloc(endpos - committer + 2);
+                       memcpy(add_signoff, committer, endpos - committer + 1);
+                       add_signoff[endpos - committer + 1] = 0;
+               }
+               else if (!strcmp(argv[i], "--attach"))
+                       rev.mime_boundary = git_version_string;
+               else if (!strncmp(argv[i], "--attach=", 9))
+                       rev.mime_boundary = argv[i] + 9;
+               else
+                       argv[j++] = argv[i];
+       }
+       argc = j;
+
+       if (start_number < 0)
+               start_number = 1;
+       if (numbered && keep_subject)
+               die ("-n and -k are mutually exclusive.");
+
+       argc = setup_revisions(argc, argv, &rev, "HEAD");
+       if (argc > 1)
+               die ("unrecognized argument: %s", argv[1]);
+
+       if (output_directory) {
+               if (use_stdout)
+                       die("standard output, or directory, which one?");
+               if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
+                       die("Could not create directory %s",
+                           output_directory);
+       }
+
+       if (rev.pending_objects && rev.pending_objects->next == NULL) {
+               rev.pending_objects->item->flags |= UNINTERESTING;
+               add_head(&rev);
+       }
+
+       if (!use_stdout)
+               realstdout = fdopen(dup(1), "w");
+
+       prepare_revision_walk(&rev);
+       while ((commit = get_revision(&rev)) != NULL) {
+               /* ignore merges */
+               if (commit->parents && commit->parents->next)
+                       continue;
+               nr++;
+               list = realloc(list, nr * sizeof(list[0]));
+               list[nr - 1] = commit;
+       }
+       total = nr;
+       if (numbered)
+               rev.total = total + start_number - 1;
+       rev.add_signoff = add_signoff;
+       while (0 <= --nr) {
+               int shown;
+               commit = list[nr];
+               rev.nr = total - nr + (start_number - 1);
+               if (!use_stdout)
+                       reopen_stdout(commit, rev.nr, keep_subject);
+               shown = log_tree_commit(&rev, commit);
+               free(commit->buffer);
+               commit->buffer = NULL;
+
+               /* We put one extra blank line between formatted
+                * patches and this flag is used by log-tree code
+                * to see if it needs to emit a LF before showing
+                * the log; when using one file per patch, we do
+                * not want the extra blank line.
+                */
+               if (!use_stdout)
+                       rev.shown_one = 0;
+               if (shown) {
+                       if (rev.mime_boundary)
+                               printf("\n--%s%s--\n\n\n",
+                                      mime_boundary_leader,
+                                      rev.mime_boundary);
+                       else
+                               printf("-- \n%s\n\n", git_version_string);
+               }
+               if (!use_stdout)
+                       fclose(stdout);
+       }
+       free(list);
+       return 0;
+}
+
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
new file mode 100644 (file)
index 0000000..8dae9f7
--- /dev/null
@@ -0,0 +1,499 @@
+/*
+ * This merges the file listing in the directory cache index
+ * with the actual working directory list, and shows different
+ * combinations of the two.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "quote.h"
+#include "dir.h"
+#include "builtin.h"
+
+static int abbrev = 0;
+static int show_deleted = 0;
+static int show_cached = 0;
+static int show_others = 0;
+static int show_stage = 0;
+static int show_unmerged = 0;
+static int show_modified = 0;
+static int show_killed = 0;
+static int show_valid_bit = 0;
+static int line_terminator = '\n';
+
+static int prefix_len = 0, prefix_offset = 0;
+static const char *prefix = NULL;
+static const char **pathspec = NULL;
+static int error_unmatch = 0;
+static char *ps_matched = NULL;
+
+static const char *tag_cached = "";
+static const char *tag_unmerged = "";
+static const char *tag_removed = "";
+static const char *tag_other = "";
+static const char *tag_killed = "";
+static const char *tag_modified = "";
+
+
+/*
+ * Match a pathspec against a filename. The first "len" characters
+ * are the common prefix
+ */
+static int match(const char **spec, char *ps_matched,
+                const char *filename, int len)
+{
+       const char *m;
+
+       while ((m = *spec++) != NULL) {
+               int matchlen = strlen(m + len);
+
+               if (!matchlen)
+                       goto matched;
+               if (!strncmp(m + len, filename + len, matchlen)) {
+                       if (m[len + matchlen - 1] == '/')
+                               goto matched;
+                       switch (filename[len + matchlen]) {
+                       case '/': case '\0':
+                               goto matched;
+                       }
+               }
+               if (!fnmatch(m + len, filename + len, 0))
+                       goto matched;
+               if (ps_matched)
+                       ps_matched++;
+               continue;
+       matched:
+               if (ps_matched)
+                       *ps_matched = 1;
+               return 1;
+       }
+       return 0;
+}
+
+static void show_dir_entry(const char *tag, struct dir_entry *ent)
+{
+       int len = prefix_len;
+       int offset = prefix_offset;
+
+       if (len >= ent->len)
+               die("git-ls-files: internal error - directory entry not superset of prefix");
+
+       if (pathspec && !match(pathspec, ps_matched, ent->name, len))
+               return;
+
+       fputs(tag, stdout);
+       write_name_quoted("", 0, ent->name + offset, line_terminator, stdout);
+       putchar(line_terminator);
+}
+
+static void show_other_files(struct dir_struct *dir)
+{
+       int i;
+       for (i = 0; i < dir->nr; i++) {
+               /* We should not have a matching entry, but we
+                * may have an unmerged entry for this path.
+                */
+               struct dir_entry *ent = dir->entries[i];
+               int pos = cache_name_pos(ent->name, ent->len);
+               struct cache_entry *ce;
+               if (0 <= pos)
+                       die("bug in show-other-files");
+               pos = -pos - 1;
+               if (pos < active_nr) { 
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) == ent->len &&
+                           !memcmp(ce->name, ent->name, ent->len))
+                               continue; /* Yup, this one exists unmerged */
+               }
+               show_dir_entry(tag_other, ent);
+       }
+}
+
+static void show_killed_files(struct dir_struct *dir)
+{
+       int i;
+       for (i = 0; i < dir->nr; i++) {
+               struct dir_entry *ent = dir->entries[i];
+               char *cp, *sp;
+               int pos, len, killed = 0;
+
+               for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
+                       sp = strchr(cp, '/');
+                       if (!sp) {
+                               /* If ent->name is prefix of an entry in the
+                                * cache, it will be killed.
+                                */
+                               pos = cache_name_pos(ent->name, ent->len);
+                               if (0 <= pos)
+                                       die("bug in show-killed-files");
+                               pos = -pos - 1;
+                               while (pos < active_nr &&
+                                      ce_stage(active_cache[pos]))
+                                       pos++; /* skip unmerged */
+                               if (active_nr <= pos)
+                                       break;
+                               /* pos points at a name immediately after
+                                * ent->name in the cache.  Does it expect
+                                * ent->name to be a directory?
+                                */
+                               len = ce_namelen(active_cache[pos]);
+                               if ((ent->len < len) &&
+                                   !strncmp(active_cache[pos]->name,
+                                            ent->name, ent->len) &&
+                                   active_cache[pos]->name[ent->len] == '/')
+                                       killed = 1;
+                               break;
+                       }
+                       if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
+                               /* If any of the leading directories in
+                                * ent->name is registered in the cache,
+                                * ent->name will be killed.
+                                */
+                               killed = 1;
+                               break;
+                       }
+               }
+               if (killed)
+                       show_dir_entry(tag_killed, dir->entries[i]);
+       }
+}
+
+static void show_ce_entry(const char *tag, struct cache_entry *ce)
+{
+       int len = prefix_len;
+       int offset = prefix_offset;
+
+       if (len >= ce_namelen(ce))
+               die("git-ls-files: internal error - cache entry not superset of prefix");
+
+       if (pathspec && !match(pathspec, ps_matched, ce->name, len))
+               return;
+
+       if (tag && *tag && show_valid_bit &&
+           (ce->ce_flags & htons(CE_VALID))) {
+               static char alttag[4];
+               memcpy(alttag, tag, 3);
+               if (isalpha(tag[0]))
+                       alttag[0] = tolower(tag[0]);
+               else if (tag[0] == '?')
+                       alttag[0] = '!';
+               else {
+                       alttag[0] = 'v';
+                       alttag[1] = tag[0];
+                       alttag[2] = ' ';
+                       alttag[3] = 0;
+               }
+               tag = alttag;
+       }
+
+       if (!show_stage) {
+               fputs(tag, stdout);
+               write_name_quoted("", 0, ce->name + offset,
+                                 line_terminator, stdout);
+               putchar(line_terminator);
+       }
+       else {
+               printf("%s%06o %s %d\t",
+                      tag,
+                      ntohl(ce->ce_mode),
+                      abbrev ? find_unique_abbrev(ce->sha1,abbrev)
+                               : sha1_to_hex(ce->sha1),
+                      ce_stage(ce));
+               write_name_quoted("", 0, ce->name + offset,
+                                 line_terminator, stdout);
+               putchar(line_terminator);
+       }
+}
+
+static void show_files(struct dir_struct *dir)
+{
+       int i;
+
+       /* For cached/deleted files we don't need to even do the readdir */
+       if (show_others || show_killed) {
+               const char *path = ".", *base = "";
+               int baselen = prefix_len;
+
+               if (baselen)
+                       path = base = prefix;
+               read_directory(dir, path, base, baselen);
+               if (show_others)
+                       show_other_files(dir);
+               if (show_killed)
+                       show_killed_files(dir);
+       }
+       if (show_cached | show_stage) {
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       if (excluded(dir, ce->name) != dir->show_ignored)
+                               continue;
+                       if (show_unmerged && !ce_stage(ce))
+                               continue;
+                       show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
+               }
+       }
+       if (show_deleted | show_modified) {
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       struct stat st;
+                       int err;
+                       if (excluded(dir, ce->name) != dir->show_ignored)
+                               continue;
+                       err = lstat(ce->name, &st);
+                       if (show_deleted && err)
+                               show_ce_entry(tag_removed, ce);
+                       if (show_modified && ce_modified(ce, &st, 0))
+                               show_ce_entry(tag_modified, ce);
+               }
+       }
+}
+
+/*
+ * Prune the index to only contain stuff starting with "prefix"
+ */
+static void prune_cache(void)
+{
+       int pos = cache_name_pos(prefix, prefix_len);
+       unsigned int first, last;
+
+       if (pos < 0)
+               pos = -pos-1;
+       active_cache += pos;
+       active_nr -= pos;
+       first = 0;
+       last = active_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct cache_entry *ce = active_cache[next];
+               if (!strncmp(ce->name, prefix, prefix_len)) {
+                       first = next+1;
+                       continue;
+               }
+               last = next;
+       }
+       active_nr = last;
+}
+
+static void verify_pathspec(void)
+{
+       const char **p, *n, *prev;
+       char *real_prefix;
+       unsigned long max;
+
+       prev = NULL;
+       max = PATH_MAX;
+       for (p = pathspec; (n = *p) != NULL; p++) {
+               int i, len = 0;
+               for (i = 0; i < max; i++) {
+                       char c = n[i];
+                       if (prev && prev[i] != c)
+                               break;
+                       if (!c || c == '*' || c == '?')
+                               break;
+                       if (c == '/')
+                               len = i+1;
+               }
+               prev = n;
+               if (len < max) {
+                       max = len;
+                       if (!max)
+                               break;
+               }
+       }
+
+       if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
+               die("git-ls-files: cannot generate relative filenames containing '..'");
+
+       real_prefix = NULL;
+       prefix_len = max;
+       if (max) {
+               real_prefix = xmalloc(max + 1);
+               memcpy(real_prefix, prev, max);
+               real_prefix[max] = 0;
+       }
+       prefix = real_prefix;
+}
+
+static const char ls_files_usage[] =
+       "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+       "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
+       "[ --exclude-per-directory=<filename> ] [--full-name] [--abbrev] "
+       "[--] [<file>]*";
+
+int cmd_ls_files(int argc, const char **argv, char** envp)
+{
+       int i;
+       int exc_given = 0;
+       struct dir_struct dir;
+
+       memset(&dir, 0, sizeof(dir));
+       prefix = setup_git_directory();
+       if (prefix)
+               prefix_offset = strlen(prefix);
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_terminator = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
+                       tag_cached = "H ";
+                       tag_unmerged = "M ";
+                       tag_removed = "R ";
+                       tag_modified = "C ";
+                       tag_other = "? ";
+                       tag_killed = "K ";
+                       if (arg[1] == 'v')
+                               show_valid_bit = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
+                       show_cached = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
+                       show_deleted = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
+                       show_modified = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
+                       show_others = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
+                       dir.show_ignored = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
+                       show_stage = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
+                       show_killed = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--directory")) {
+                       dir.show_other_directories = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-empty-directory")) {
+                       dir.hide_empty_directories = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
+                       /* There's no point in showing unmerged unless
+                        * you also show the stage information.
+                        */
+                       show_stage = 1;
+                       show_unmerged = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-x") && i+1 < argc) {
+                       exc_given = 1;
+                       add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
+                       continue;
+               }
+               if (!strncmp(arg, "--exclude=", 10)) {
+                       exc_given = 1;
+                       add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
+                       continue;
+               }
+               if (!strcmp(arg, "-X") && i+1 < argc) {
+                       exc_given = 1;
+                       add_excludes_from_file(&dir, argv[++i]);
+                       continue;
+               }
+               if (!strncmp(arg, "--exclude-from=", 15)) {
+                       exc_given = 1;
+                       add_excludes_from_file(&dir, arg+15);
+                       continue;
+               }
+               if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+                       exc_given = 1;
+                       dir.exclude_per_dir = arg + 24;
+                       continue;
+               }
+               if (!strcmp(arg, "--full-name")) {
+                       prefix_offset = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--error-unmatch")) {
+                       error_unmatch = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = strtoul(arg+9, NULL, 10);
+                       if (abbrev && abbrev < MINIMUM_ABBREV)
+                               abbrev = MINIMUM_ABBREV;
+                       else if (abbrev > 40)
+                               abbrev = 40;
+                       continue;
+               }
+               if (!strcmp(arg, "--abbrev")) {
+                       abbrev = DEFAULT_ABBREV;
+                       continue;
+               }
+               if (*arg == '-')
+                       usage(ls_files_usage);
+               break;
+       }
+
+       pathspec = get_pathspec(prefix, argv + i);
+
+       /* Verify that the pathspec matches the prefix */
+       if (pathspec)
+               verify_pathspec();
+
+       /* Treat unmatching pathspec elements as errors */
+       if (pathspec && error_unmatch) {
+               int num;
+               for (num = 0; pathspec[num]; num++)
+                       ;
+               ps_matched = xcalloc(1, num);
+       }
+
+       if (dir.show_ignored && !exc_given) {
+               fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
+                       argv[0]);
+               exit(1);
+       }
+
+       /* With no flags, we default to showing the cached files */
+       if (!(show_stage | show_deleted | show_others | show_unmerged |
+             show_killed | show_modified))
+               show_cached = 1;
+
+       read_cache();
+       if (prefix)
+               prune_cache();
+       show_files(&dir);
+
+       if (ps_matched) {
+               /* We need to make sure all pathspec matched otherwise
+                * it is an error.
+                */
+               int num, errors = 0;
+               for (num = 0; pathspec[num]; num++) {
+                       if (ps_matched[num])
+                               continue;
+                       error("pathspec '%s' did not match any.",
+                             pathspec[num] + prefix_offset);
+                       errors++;
+               }
+               return errors ? 1 : 0;
+       }
+
+       return 0;
+}
diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c
new file mode 100644 (file)
index 0000000..b8d0d88
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "quote.h"
+#include "builtin.h"
+
+static int line_termination = '\n';
+#define LS_RECURSIVE 1
+#define LS_TREE_ONLY 2
+#define LS_SHOW_TREES 4
+#define LS_NAME_ONLY 8
+static int abbrev = 0;
+static int ls_options = 0;
+static const char **pathspec;
+static int chomp_prefix = 0;
+static const char *prefix;
+
+static const char ls_tree_usage[] =
+       "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
+
+static int show_recursive(const char *base, int baselen, const char *pathname)
+{
+       const char **s;
+
+       if (ls_options & LS_RECURSIVE)
+               return 1;
+
+       s = pathspec;
+       if (!s)
+               return 0;
+
+       for (;;) {
+               const char *spec = *s++;
+               int len, speclen;
+
+               if (!spec)
+                       return 0;
+               if (strncmp(base, spec, baselen))
+                       continue;
+               len = strlen(pathname);
+               spec += baselen;
+               speclen = strlen(spec);
+               if (speclen <= len)
+                       continue;
+               if (memcmp(pathname, spec, len))
+                       continue;
+               return 1;
+       }
+}
+
+static int show_tree(const unsigned char *sha1, const char *base, int baselen,
+                    const char *pathname, unsigned mode, int stage)
+{
+       int retval = 0;
+       const char *type = blob_type;
+
+       if (S_ISDIR(mode)) {
+               if (show_recursive(base, baselen, pathname)) {
+                       retval = READ_TREE_RECURSIVE;
+                       if (!(ls_options & LS_SHOW_TREES))
+                               return retval;
+               }
+               type = tree_type;
+       }
+       else if (ls_options & LS_TREE_ONLY)
+               return 0;
+
+       if (chomp_prefix &&
+           (baselen < chomp_prefix || memcmp(prefix, base, chomp_prefix)))
+               return 0;
+
+       if (!(ls_options & LS_NAME_ONLY))
+               printf("%06o %s %s\t", mode, type,
+                               abbrev ? find_unique_abbrev(sha1,abbrev)
+                                       : sha1_to_hex(sha1));
+       write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
+                         pathname,
+                         line_termination, stdout);
+       putchar(line_termination);
+       return retval;
+}
+
+int cmd_ls_tree(int argc, const char **argv, char **envp)
+{
+       unsigned char sha1[20];
+       struct tree *tree;
+
+       prefix = setup_git_directory();
+       git_config(git_default_config);
+       if (prefix && *prefix)
+               chomp_prefix = strlen(prefix);
+       while (1 < argc && argv[1][0] == '-') {
+               switch (argv[1][1]) {
+               case 'z':
+                       line_termination = 0;
+                       break;
+               case 'r':
+                       ls_options |= LS_RECURSIVE;
+                       break;
+               case 'd':
+                       ls_options |= LS_TREE_ONLY;
+                       break;
+               case 't':
+                       ls_options |= LS_SHOW_TREES;
+                       break;
+               case '-':
+                       if (!strcmp(argv[1]+2, "name-only") ||
+                           !strcmp(argv[1]+2, "name-status")) {
+                               ls_options |= LS_NAME_ONLY;
+                               break;
+                       }
+                       if (!strcmp(argv[1]+2, "full-name")) {
+                               chomp_prefix = 0;
+                               break;
+                       }
+                       if (!strncmp(argv[1]+2, "abbrev=",7)) {
+                               abbrev = strtoul(argv[1]+9, NULL, 10);
+                               if (abbrev && abbrev < MINIMUM_ABBREV)
+                                       abbrev = MINIMUM_ABBREV;
+                               else if (abbrev > 40)
+                                       abbrev = 40;
+                               break;
+                       }
+                       if (!strcmp(argv[1]+2, "abbrev")) {
+                               abbrev = DEFAULT_ABBREV;
+                               break;
+                       }
+                       /* otherwise fallthru */
+               default:
+                       usage(ls_tree_usage);
+               }
+               argc--; argv++;
+       }
+       /* -d -r should imply -t, but -d by itself should not have to. */
+       if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
+           ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
+               ls_options |= LS_SHOW_TREES;
+
+       if (argc < 2)
+               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       pathspec = get_pathspec(prefix, argv + 2);
+       tree = parse_tree_indirect(sha1);
+       if (!tree)
+               die("not a tree object");
+       read_tree_recursive(tree, "", 0, 0, pathspec, show_tree);
+
+       return 0;
+}
diff --git a/builtin-push.c b/builtin-push.c
new file mode 100644 (file)
index 0000000..66b9407
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+
+#define MAX_URI (16)
+
+static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";
+
+static int all = 0, tags = 0, force = 0, thin = 1;
+static const char *execute = NULL;
+
+#define BUF_SIZE (2084)
+static char buffer[BUF_SIZE];
+
+static const char **refspec = NULL;
+static int refspec_nr = 0;
+
+static void add_refspec(const char *ref)
+{
+       int nr = refspec_nr + 1;
+       refspec = xrealloc(refspec, nr * sizeof(char *));
+       refspec[nr-1] = ref;
+       refspec_nr = nr;
+}
+
+static int expand_one_ref(const char *ref, const unsigned char *sha1)
+{
+       /* Ignore the "refs/" at the beginning of the refname */
+       ref += 5;
+
+       if (strncmp(ref, "tags/", 5))
+               return 0;
+
+       add_refspec(strdup(ref));
+       return 0;
+}
+
+static void expand_refspecs(void)
+{
+       if (all) {
+               if (refspec_nr)
+                       die("cannot mix '--all' and a refspec");
+
+               /*
+                * No need to expand "--all" - we'll just use
+                * the "--all" flag to send-pack
+                */
+               return;
+       }
+       if (!tags)
+               return;
+       for_each_ref(expand_one_ref);
+}
+
+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;
+       }
+       expand_refspecs();
+}
+
+static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       int n = 0;
+       FILE *f = fopen(git_path("remotes/%s", repo), "r");
+       int has_explicit_refspec = refspec_nr || all || tags;
+
+       if (!f)
+               return -1;
+       while (fgets(buffer, BUF_SIZE, f)) {
+               int is_refspec;
+               char *s, *p;
+
+               if (!strncmp("URL: ", buffer, 5)) {
+                       is_refspec = 0;
+                       s = buffer + 5;
+               } else if (!strncmp("Push: ", buffer, 6)) {
+                       is_refspec = 1;
+                       s = buffer + 6;
+               } else
+                       continue;
+
+               /* Remove whitespace at the head.. */
+               while (isspace(*s))
+                       s++;
+               if (!*s)
+                       continue;
+
+               /* ..and at the end */
+               p = s + strlen(s);
+               while (isspace(p[-1]))
+                       *--p = 0;
+
+               if (!is_refspec) {
+                       if (n < MAX_URI)
+                               uri[n++] = strdup(s);
+                       else
+                               error("more than %d URL's specified, ignoreing the rest", MAX_URI);
+               }
+               else if (is_refspec && !has_explicit_refspec)
+                       add_refspec(strdup(s));
+       }
+       fclose(f);
+       if (!n)
+               die("remote '%s' has no URL", repo);
+       return n;
+}
+
+static const char **config_uri;
+static const char *config_repo;
+static int config_repo_len;
+static int config_current_uri;
+static int config_get_refspecs;
+
+static int get_remote_config(const char* key, const char* value)
+{
+       if (!strncmp(key, "remote.", 7) &&
+           !strncmp(key + 7, config_repo, config_repo_len)) {
+               if (!strcmp(key + 7 + config_repo_len, ".url")) {
+                       if (config_current_uri < MAX_URI)
+                               config_uri[config_current_uri++] = strdup(value);
+                       else
+                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
+               }
+               else if (config_get_refspecs &&
+                        !strcmp(key + 7 + config_repo_len, ".push"))
+                       add_refspec(strdup(value));
+       }
+       return 0;
+}
+
+static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       config_repo_len = strlen(repo);
+       config_repo = repo;
+       config_current_uri = 0;
+       config_uri = uri;
+       config_get_refspecs = !(refspec_nr || all || tags);
+
+       git_config(get_remote_config);
+       return config_current_uri;
+}
+
+static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
+{
+       const char *slash = strchr(repo, '/');
+       int n = slash ? slash - repo : 1000;
+       FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
+       char *s, *p;
+       int len;
+
+       if (!f)
+               return 0;
+       s = fgets(buffer, BUF_SIZE, f);
+       fclose(f);
+       if (!s)
+               return 0;
+       while (isspace(*s))
+               s++;
+       if (!*s)
+               return 0;
+       p = s + strlen(s);
+       while (isspace(p[-1]))
+               *--p = 0;
+       len = p - s;
+       if (slash)
+               len += strlen(slash);
+       p = xmalloc(len + 1);
+       strcpy(p, s);
+       if (slash)
+               strcat(p, slash);
+       uri[0] = p;
+       return 1;
+}
+
+/*
+ * Read remotes and branches file, fill the push target URI
+ * list.  If there is no command line refspecs, read Push: lines
+ * to set up the *refspec list as well.
+ * return the number of push target URIs
+ */
+static int read_config(const char *repo, const char *uri[MAX_URI])
+{
+       int n;
+
+       if (*repo != '/') {
+               n = get_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_config_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_branches_uri(repo, uri);
+               if (n > 0)
+                       return n;
+       }
+
+       uri[0] = repo;
+       return 1;
+}
+
+static int do_push(const char *repo)
+{
+       const char *uri[MAX_URI];
+       int i, n;
+       int common_argc;
+       const char **argv;
+       int argc;
+
+       n = read_config(repo, uri);
+       if (n <= 0)
+               die("bad repository '%s'", repo);
+
+       argv = xmalloc((refspec_nr + 10) * sizeof(char *));
+       argv[0] = "dummy-send-pack";
+       argc = 1;
+       if (all)
+               argv[argc++] = "--all";
+       if (force)
+               argv[argc++] = "--force";
+       if (execute)
+               argv[argc++] = execute;
+       common_argc = argc;
+
+       for (i = 0; i < n; i++) {
+               int error;
+               int dest_argc = common_argc;
+               int dest_refspec_nr = refspec_nr;
+               const char **dest_refspec = refspec;
+               const char *dest = uri[i];
+               const char *sender = "git-send-pack";
+               if (!strncmp(dest, "http://", 7) ||
+                   !strncmp(dest, "https://", 8))
+                       sender = "git-http-push";
+               else if (thin)
+                       argv[dest_argc++] = "--thin";
+               argv[0] = sender;
+               argv[dest_argc++] = dest;
+               while (dest_refspec_nr--)
+                       argv[dest_argc++] = *dest_refspec++;
+               argv[dest_argc] = NULL;
+               error = run_command_v(argc, argv);
+               if (!error)
+                       continue;
+               switch (error) {
+               case -ERR_RUN_COMMAND_FORK:
+                       die("unable to fork for %s", sender);
+               case -ERR_RUN_COMMAND_EXEC:
+                       die("unable to exec %s", sender);
+               case -ERR_RUN_COMMAND_WAITPID:
+               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+                       die("%s died with strange error", sender);
+               default:
+                       return -error;
+               }
+       }
+       return 0;
+}
+
+int cmd_push(int argc, const char **argv, char **envp)
+{
+       int i;
+       const char *repo = "origin";    // default repository
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-') {
+                       repo = arg;
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--force")) {
+                       force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--thin")) {
+                       thin = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-thin")) {
+                       thin = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "--exec=", 7)) {
+                       execute = arg;
+                       continue;
+               }
+               usage(push_usage);
+       }
+       set_refspecs(argv + i, argc - i);
+       return do_push(repo);
+}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
new file mode 100644 (file)
index 0000000..bb50fbd
--- /dev/null
@@ -0,0 +1,987 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#define DBRT_DEBUG 1
+
+#include "cache.h"
+
+#include "object.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "cache-tree.h"
+#include <sys/time.h>
+#include <signal.h>
+#include "builtin.h"
+
+static int reset = 0;
+static int merge = 0;
+static int update = 0;
+static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
+static int aggressive = 0;
+static int verbose_update = 0;
+static volatile int progress_update = 0;
+
+static int head_idx = -1;
+static int merge_size = 0;
+
+static struct object_list *trees = NULL;
+
+static struct cache_entry df_conflict_entry = {
+};
+
+struct tree_entry_list {
+       struct tree_entry_list *next;
+       unsigned directory : 1;
+       unsigned executable : 1;
+       unsigned symlink : 1;
+       unsigned int mode;
+       const char *name;
+       const unsigned char *sha1;
+};
+
+static struct tree_entry_list df_conflict_list = {
+       .name = NULL,
+       .next = &df_conflict_list
+};
+
+typedef int (*merge_fn_t)(struct cache_entry **src);
+
+static struct tree_entry_list *create_tree_entry_list(struct tree *tree)
+{
+       struct tree_desc desc;
+       struct name_entry one;
+       struct tree_entry_list *ret = NULL;
+       struct tree_entry_list **list_p = &ret;
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+
+       while (tree_entry(&desc, &one)) {
+               struct tree_entry_list *entry;
+
+               entry = xmalloc(sizeof(struct tree_entry_list));
+               entry->name = one.path;
+               entry->sha1 = one.sha1;
+               entry->mode = one.mode;
+               entry->directory = S_ISDIR(one.mode) != 0;
+               entry->executable = (one.mode & S_IXUSR) != 0;
+               entry->symlink = S_ISLNK(one.mode) != 0;
+               entry->next = NULL;
+
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+       return ret;
+}
+
+static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
+{
+       int len1 = strlen(name1);
+       int len2 = strlen(name2);
+       int len = len1 < len2 ? len1 : len2;
+       int ret = memcmp(name1, name2, len);
+       unsigned char c1, c2;
+       if (ret)
+               return ret;
+       c1 = name1[len];
+       c2 = name2[len];
+       if (!c1 && dir1)
+               c1 = '/';
+       if (!c2 && dir2)
+               c2 = '/';
+       ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+       if (c1 && c2 && !ret)
+               ret = len1 - len2;
+       return ret;
+}
+
+static int unpack_trees_rec(struct tree_entry_list **posns, int len,
+                           const char *base, merge_fn_t fn, int *indpos)
+{
+       int baselen = strlen(base);
+       int src_size = len + 1;
+       do {
+               int i;
+               const char *first;
+               int firstdir = 0;
+               int pathlen;
+               unsigned ce_size;
+               struct tree_entry_list **subposns;
+               struct cache_entry **src;
+               int any_files = 0;
+               int any_dirs = 0;
+               char *cache_name;
+               int ce_stage;
+
+               /* Find the first name in the input. */
+
+               first = NULL;
+               cache_name = NULL;
+
+               /* Check the cache */
+               if (merge && *indpos < active_nr) {
+                       /* This is a bit tricky: */
+                       /* If the index has a subdirectory (with
+                        * contents) as the first name, it'll get a
+                        * filename like "foo/bar". But that's after
+                        * "foo", so the entry in trees will get
+                        * handled first, at which point we'll go into
+                        * "foo", and deal with "bar" from the index,
+                        * because the base will be "foo/". The only
+                        * way we can actually have "foo/bar" first of
+                        * all the things is if the trees don't
+                        * contain "foo" at all, in which case we'll
+                        * handle "foo/bar" without going into the
+                        * directory, but that's fine (and will return
+                        * an error anyway, with the added unknown
+                        * file case.
+                        */
+
+                       cache_name = active_cache[*indpos]->name;
+                       if (strlen(cache_name) > baselen &&
+                           !memcmp(cache_name, base, baselen)) {
+                               cache_name += baselen;
+                               first = cache_name;
+                       } else {
+                               cache_name = NULL;
+                       }
+               }
+
+#if DBRT_DEBUG > 1
+               if (first)
+                       printf("index %s\n", first);
+#endif
+               for (i = 0; i < len; i++) {
+                       if (!posns[i] || posns[i] == &df_conflict_list)
+                               continue;
+#if DBRT_DEBUG > 1
+                       printf("%d %s\n", i + 1, posns[i]->name);
+#endif
+                       if (!first || entcmp(first, firstdir,
+                                            posns[i]->name, 
+                                            posns[i]->directory) > 0) {
+                               first = posns[i]->name;
+                               firstdir = posns[i]->directory;
+                       }
+               }
+               /* No name means we're done */
+               if (!first)
+                       return 0;
+
+               pathlen = strlen(first);
+               ce_size = cache_entry_size(baselen + pathlen);
+
+               src = xcalloc(src_size, sizeof(struct cache_entry *));
+
+               subposns = xcalloc(len, sizeof(struct tree_list_entry *));
+
+               if (cache_name && !strcmp(cache_name, first)) {
+                       any_files = 1;
+                       src[0] = active_cache[*indpos];
+                       remove_cache_entry_at(*indpos);
+               }
+
+               for (i = 0; i < len; i++) {
+                       struct cache_entry *ce;
+
+                       if (!posns[i] ||
+                           (posns[i] != &df_conflict_list &&
+                            strcmp(first, posns[i]->name))) {
+                               continue;
+                       }
+
+                       if (posns[i] == &df_conflict_list) {
+                               src[i + merge] = &df_conflict_entry;
+                               continue;
+                       }
+
+                       if (posns[i]->directory) {
+                               struct tree *tree = lookup_tree(posns[i]->sha1);
+                               any_dirs = 1;
+                               parse_tree(tree);
+                               subposns[i] = create_tree_entry_list(tree);
+                               posns[i] = posns[i]->next;
+                               src[i + merge] = &df_conflict_entry;
+                               continue;
+                       }
+
+                       if (!merge)
+                               ce_stage = 0;
+                       else if (i + 1 < head_idx)
+                               ce_stage = 1;
+                       else if (i + 1 > head_idx)
+                               ce_stage = 3;
+                       else
+                               ce_stage = 2;
+
+                       ce = xcalloc(1, ce_size);
+                       ce->ce_mode = create_ce_mode(posns[i]->mode);
+                       ce->ce_flags = create_ce_flags(baselen + pathlen,
+                                                      ce_stage);
+                       memcpy(ce->name, base, baselen);
+                       memcpy(ce->name + baselen, first, pathlen + 1);
+
+                       any_files = 1;
+
+                       memcpy(ce->sha1, posns[i]->sha1, 20);
+                       src[i + merge] = ce;
+                       subposns[i] = &df_conflict_list;
+                       posns[i] = posns[i]->next;
+               }
+               if (any_files) {
+                       if (merge) {
+                               int ret;
+
+#if DBRT_DEBUG > 1
+                               printf("%s:\n", first);
+                               for (i = 0; i < src_size; i++) {
+                                       printf(" %d ", i);
+                                       if (src[i])
+                                               printf("%s\n", sha1_to_hex(src[i]->sha1));
+                                       else
+                                               printf("\n");
+                               }
+#endif
+                               ret = fn(src);
+                               
+#if DBRT_DEBUG > 1
+                               printf("Added %d entries\n", ret);
+#endif
+                               *indpos += ret;
+                       } else {
+                               for (i = 0; i < src_size; i++) {
+                                       if (src[i]) {
+                                               add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+                                       }
+                               }
+                       }
+               }
+               if (any_dirs) {
+                       char *newbase = xmalloc(baselen + 2 + pathlen);
+                       memcpy(newbase, base, baselen);
+                       memcpy(newbase + baselen, first, pathlen);
+                       newbase[baselen + pathlen] = '/';
+                       newbase[baselen + pathlen + 1] = '\0';
+                       if (unpack_trees_rec(subposns, len, newbase, fn,
+                                            indpos))
+                               return -1;
+                       free(newbase);
+               }
+               free(subposns);
+               free(src);
+       } while (1);
+}
+
+static void reject_merge(struct cache_entry *ce)
+{
+       die("Entry '%s' would be overwritten by merge. Cannot merge.", 
+           ce->name);
+}
+
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+       char *cp, *prev;
+
+       if (unlink(name))
+               return;
+       prev = NULL;
+       while (1) {
+               int status;
+               cp = strrchr(name, '/');
+               if (prev)
+                       *prev = '/';
+               if (!cp)
+                       break;
+
+               *cp = 0;
+               status = rmdir(name);
+               if (status) {
+                       *cp = '/';
+                       break;
+               }
+               prev = cp;
+       }
+}
+
+static void progress_interval(int signum)
+{
+       progress_update = 1;
+}
+
+static void setup_progress_signal(void)
+{
+       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 check_updates(struct cache_entry **src, int nr)
+{
+       static struct checkout state = {
+               .base_dir = "",
+               .force = 1,
+               .quiet = 1,
+               .refresh_cache = 1,
+       };
+       unsigned short mask = htons(CE_UPDATE);
+       unsigned last_percent = 200, cnt = 0, total = 0;
+
+       if (update && verbose_update) {
+               for (total = cnt = 0; cnt < nr; cnt++) {
+                       struct cache_entry *ce = src[cnt];
+                       if (!ce->ce_mode || ce->ce_flags & mask)
+                               total++;
+               }
+
+               /* Don't bother doing this for very small updates */
+               if (total < 250)
+                       total = 0;
+
+               if (total) {
+                       fprintf(stderr, "Checking files out...\n");
+                       setup_progress_signal();
+                       progress_update = 1;
+               }
+               cnt = 0;
+       }
+
+       while (nr--) {
+               struct cache_entry *ce = *src++;
+
+               if (total) {
+                       if (!ce->ce_mode || ce->ce_flags & mask) {
+                               unsigned percent;
+                               cnt++;
+                               percent = (cnt * 100) / total;
+                               if (percent != last_percent ||
+                                   progress_update) {
+                                       fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                               percent, cnt, total);
+                                       last_percent = percent;
+                                       progress_update = 0;
+                               }
+                       }
+               }
+               if (!ce->ce_mode) {
+                       if (update)
+                               unlink_entry(ce->name);
+                       continue;
+               }
+               if (ce->ce_flags & mask) {
+                       ce->ce_flags &= ~mask;
+                       if (update)
+                               checkout_entry(ce, &state, NULL);
+               }
+       }
+       if (total) {
+               signal(SIGALRM, SIG_IGN);
+               fputc('\n', stderr);
+       }
+}
+
+static int unpack_trees(merge_fn_t fn)
+{
+       int indpos = 0;
+       unsigned len = object_list_length(trees);
+       struct tree_entry_list **posns;
+       int i;
+       struct object_list *posn = trees;
+       merge_size = len;
+
+       if (len) {
+               posns = xmalloc(len * sizeof(struct tree_entry_list *));
+               for (i = 0; i < len; i++) {
+                       posns[i] = create_tree_entry_list((struct tree *) posn->item);
+                       posn = posn->next;
+               }
+               if (unpack_trees_rec(posns, len, "", fn, &indpos))
+                       return -1;
+       }
+
+       if (trivial_merges_only && nontrivial_merge)
+               die("Merge requires file-level merging");
+
+       check_updates(active_cache, active_nr);
+       return 0;
+}
+
+static int list_tree(unsigned char *sha1)
+{
+       struct tree *tree = parse_tree_indirect(sha1);
+       if (!tree)
+               return -1;
+       object_list_append(&tree->object, &trees);
+       return 0;
+}
+
+static int same(struct cache_entry *a, struct cache_entry *b)
+{
+       if (!!a != !!b)
+               return 0;
+       if (!a && !b)
+               return 1;
+       return a->ce_mode == b->ce_mode && 
+               !memcmp(a->sha1, b->sha1, 20);
+}
+
+
+/*
+ * When a CE gets turned into an unmerged entry, we
+ * want it to be up-to-date
+ */
+static void verify_uptodate(struct cache_entry *ce)
+{
+       struct stat st;
+
+       if (index_only || reset)
+               return;
+
+       if (!lstat(ce->name, &st)) {
+               unsigned changed = ce_match_stat(ce, &st, 1);
+               if (!changed)
+                       return;
+               errno = 0;
+       }
+       if (reset) {
+               ce->ce_flags |= htons(CE_UPDATE);
+               return;
+       }
+       if (errno == ENOENT)
+               return;
+       die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+}
+
+static void invalidate_ce_path(struct cache_entry *ce)
+{
+       if (ce)
+               cache_tree_invalidate_path(active_cache_tree, ce->name);
+}
+
+/*
+ * We do not want to remove or overwrite a working tree file that
+ * is not tracked.
+ */
+static void verify_absent(const char *path, const char *action)
+{
+       struct stat st;
+
+       if (index_only || reset || !update)
+               return;
+       if (!lstat(path, &st))
+               die("Untracked working tree file '%s' "
+                   "would be %s by merge.", path, action);
+}
+
+static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
+{
+       merge->ce_flags |= htons(CE_UPDATE);
+       if (old) {
+               /*
+                * See if we can re-use the old CE directly?
+                * That way we get the uptodate stat info.
+                *
+                * This also removes the UPDATE flag on
+                * a match.
+                */
+               if (same(old, merge)) {
+                       *merge = *old;
+               } else {
+                       verify_uptodate(old);
+                       invalidate_ce_path(old);
+               }
+       }
+       else {
+               verify_absent(merge->name, "overwritten");
+               invalidate_ce_path(merge);
+       }
+
+       merge->ce_flags &= ~htons(CE_STAGEMASK);
+       add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
+       return 1;
+}
+
+static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
+{
+       if (old)
+               verify_uptodate(old);
+       else
+               verify_absent(ce->name, "removed");
+       ce->ce_mode = 0;
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       invalidate_ce_path(ce);
+       return 1;
+}
+
+static int keep_entry(struct cache_entry *ce)
+{
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       return 1;
+}
+
+#if DBRT_DEBUG
+static void show_stage_entry(FILE *o,
+                            const char *label, const struct cache_entry *ce)
+{
+       if (!ce)
+               fprintf(o, "%s (missing)\n", label);
+       else
+               fprintf(o, "%s%06o %s %d\t%s\n",
+                       label,
+                       ntohl(ce->ce_mode),
+                       sha1_to_hex(ce->sha1),
+                       ce_stage(ce),
+                       ce->name);
+}
+#endif
+
+static int threeway_merge(struct cache_entry **stages)
+{
+       struct cache_entry *index;
+       struct cache_entry *head; 
+       struct cache_entry *remote = stages[head_idx + 1];
+       int count;
+       int head_match = 0;
+       int remote_match = 0;
+       const char *path = NULL;
+
+       int df_conflict_head = 0;
+       int df_conflict_remote = 0;
+
+       int any_anc_missing = 0;
+       int no_anc_exists = 1;
+       int i;
+
+       for (i = 1; i < head_idx; i++) {
+               if (!stages[i])
+                       any_anc_missing = 1;
+               else {
+                       if (!path)
+                               path = stages[i]->name;
+                       no_anc_exists = 0;
+               }
+       }
+
+       index = stages[0];
+       head = stages[head_idx];
+
+       if (head == &df_conflict_entry) {
+               df_conflict_head = 1;
+               head = NULL;
+       }
+
+       if (remote == &df_conflict_entry) {
+               df_conflict_remote = 1;
+               remote = NULL;
+       }
+
+       if (!path && index)
+               path = index->name;
+       if (!path && head)
+               path = head->name;
+       if (!path && remote)
+               path = remote->name;
+
+       /* First, if there's a #16 situation, note that to prevent #13
+        * and #14.
+        */
+       if (!same(remote, head)) {
+               for (i = 1; i < head_idx; i++) {
+                       if (same(stages[i], head)) {
+                               head_match = i;
+                       }
+                       if (same(stages[i], remote)) {
+                               remote_match = i;
+                       }
+               }
+       }
+
+       /* We start with cases where the index is allowed to match
+        * something other than the head: #14(ALT) and #2ALT, where it
+        * is permitted to match the result instead.
+        */
+       /* #14, #14ALT, #2ALT */
+       if (remote && !df_conflict_head && head_match && !remote_match) {
+               if (index && !same(index, remote) && !same(index, head))
+                       reject_merge(index);
+               return merged_entry(remote, index);
+       }
+       /*
+        * If we have an entry in the index cache, then we want to
+        * make sure that it matches head.
+        */
+       if (index && !same(index, head)) {
+               reject_merge(index);
+       }
+
+       if (head) {
+               /* #5ALT, #15 */
+               if (same(head, remote))
+                       return merged_entry(head, index);
+               /* #13, #3ALT */
+               if (!df_conflict_remote && remote_match && !head_match)
+                       return merged_entry(head, index);
+       }
+
+       /* #1 */
+       if (!head && !remote && any_anc_missing)
+               return 0;
+
+       /* Under the new "aggressive" rule, we resolve mostly trivial
+        * cases that we historically had git-merge-one-file resolve.
+        */
+       if (aggressive) {
+               int head_deleted = !head && !df_conflict_head;
+               int remote_deleted = !remote && !df_conflict_remote;
+               /*
+                * Deleted in both.
+                * Deleted in one and unchanged in the other.
+                */
+               if ((head_deleted && remote_deleted) ||
+                   (head_deleted && remote && remote_match) ||
+                   (remote_deleted && head && head_match)) {
+                       if (index)
+                               return deleted_entry(index, index);
+                       else if (path)
+                               verify_absent(path, "removed");
+                       return 0;
+               }
+               /*
+                * Added in both, identically.
+                */
+               if (no_anc_exists && head && remote && same(head, remote))
+                       return merged_entry(head, index);
+
+       }
+
+       /* Below are "no merge" cases, which require that the index be
+        * up-to-date to avoid the files getting overwritten with
+        * conflict resolution files. 
+        */
+       if (index) {
+               verify_uptodate(index);
+       }
+       else if (path)
+               verify_absent(path, "overwritten");
+
+       nontrivial_merge = 1;
+
+       /* #2, #3, #4, #6, #7, #9, #11. */
+       count = 0;
+       if (!head_match || !remote_match) {
+               for (i = 1; i < head_idx; i++) {
+                       if (stages[i]) {
+                               keep_entry(stages[i]);
+                               count++;
+                               break;
+                       }
+               }
+       }
+#if DBRT_DEBUG
+       else {
+               fprintf(stderr, "read-tree: warning #16 detected\n");
+               show_stage_entry(stderr, "head   ", stages[head_match]);
+               show_stage_entry(stderr, "remote ", stages[remote_match]);
+       }
+#endif
+       if (head) { count += keep_entry(head); }
+       if (remote) { count += keep_entry(remote); }
+       return count;
+}
+
+/*
+ * Two-way merge.
+ *
+ * The rule is to "carry forward" what is in the index without losing
+ * information across a "fast forward", favoring a successful merge
+ * over a merge failure when it makes sense.  For details of the
+ * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
+ *
+ */
+static int twoway_merge(struct cache_entry **src)
+{
+       struct cache_entry *current = src[0];
+       struct cache_entry *oldtree = src[1], *newtree = src[2];
+
+       if (merge_size != 2)
+               return error("Cannot do a twoway merge of %d trees",
+                            merge_size);
+
+       if (current) {
+               if ((!oldtree && !newtree) || /* 4 and 5 */
+                   (!oldtree && newtree &&
+                    same(current, newtree)) || /* 6 and 7 */
+                   (oldtree && newtree &&
+                    same(oldtree, newtree)) || /* 14 and 15 */
+                   (oldtree && newtree &&
+                    !same(oldtree, newtree) && /* 18 and 19*/
+                    same(current, newtree))) {
+                       return keep_entry(current);
+               }
+               else if (oldtree && !newtree && same(current, oldtree)) {
+                       /* 10 or 11 */
+                       return deleted_entry(oldtree, current);
+               }
+               else if (oldtree && newtree &&
+                        same(current, oldtree) && !same(current, newtree)) {
+                       /* 20 or 21 */
+                       return merged_entry(newtree, current);
+               }
+               else {
+                       /* all other failures */
+                       if (oldtree)
+                               reject_merge(oldtree);
+                       if (current)
+                               reject_merge(current);
+                       if (newtree)
+                               reject_merge(newtree);
+                       return -1;
+               }
+       }
+       else if (newtree)
+               return merged_entry(newtree, current);
+       else
+               return deleted_entry(oldtree, current);
+}
+
+/*
+ * One-way merge.
+ *
+ * The rule is:
+ * - take the stat information from stage0, take the data from stage1
+ */
+static int oneway_merge(struct cache_entry **src)
+{
+       struct cache_entry *old = src[0];
+       struct cache_entry *a = src[1];
+
+       if (merge_size != 1)
+               return error("Cannot do a oneway merge of %d trees",
+                            merge_size);
+
+       if (!a)
+               return deleted_entry(old, old);
+       if (old && same(old, a)) {
+               if (reset) {
+                       struct stat st;
+                       if (lstat(old->name, &st) ||
+                           ce_match_stat(old, &st, 1))
+                               old->ce_flags |= htons(CE_UPDATE);
+               }
+               return keep_entry(old);
+       }
+       return merged_entry(a, old);
+}
+
+static int read_cache_unmerged(void)
+{
+       int i;
+       struct cache_entry **dst;
+       struct cache_entry *last = NULL;
+
+       read_cache();
+       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;
+                       invalidate_ce_path(ce);
+                       last = ce;
+                       ce->ce_mode = 0;
+                       ce->ce_flags &= ~htons(CE_STAGEMASK);
+               }
+               *dst++ = ce;
+       }
+       active_nr = dst - active_cache;
+       return !!last;
+}
+
+static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       int cnt;
+
+       memcpy(it->sha1, tree->object.sha1, 20);
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+       cnt = 0;
+       while (tree_entry(&desc, &entry)) {
+               if (!S_ISDIR(entry.mode))
+                       cnt++;
+               else {
+                       struct cache_tree_sub *sub;
+                       struct tree *subtree = lookup_tree(entry.sha1);
+                       if (!subtree->object.parsed)
+                               parse_tree(subtree);
+                       sub = cache_tree_sub(it, entry.path);
+                       sub->cache_tree = cache_tree();
+                       prime_cache_tree_rec(sub->cache_tree, subtree);
+                       cnt += sub->cache_tree->entry_count;
+               }
+       }
+       it->entry_count = cnt;
+}
+
+static void prime_cache_tree(void)
+{
+       struct tree *tree = (struct tree *)trees->item;
+       if (!tree)
+               return;
+       active_cache_tree = cache_tree();
+       prime_cache_tree_rec(active_cache_tree, tree);
+
+}
+
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
+
+static struct lock_file lock_file;
+
+int cmd_read_tree(int argc, const char **argv, char **envp)
+{
+       int i, newfd, stage = 0;
+       unsigned char sha1[20];
+       merge_fn_t fn = NULL;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       newfd = hold_lock_file_for_update(&lock_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+
+       git_config(git_default_config);
+
+       merge = 0;
+       reset = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               /* "-u" means "update", meaning that a merge will update
+                * the working tree.
+                */
+               if (!strcmp(arg, "-u")) {
+                       update = 1;
+                       continue;
+               }
+
+               if (!strcmp(arg, "-v")) {
+                       verbose_update = 1;
+                       continue;
+               }
+
+               /* "-i" means "index only", meaning that a merge will
+                * not even look at the working tree.
+                */
+               if (!strcmp(arg, "-i")) {
+                       index_only = 1;
+                       continue;
+               }
+
+               /* This differs from "-m" in that we'll silently ignore
+                * unmerged entries and overwrite working tree files that
+                * correspond to them.
+                */
+               if (!strcmp(arg, "--reset")) {
+                       if (stage || merge)
+                               usage(read_tree_usage);
+                       reset = 1;
+                       merge = 1;
+                       stage = 1;
+                       read_cache_unmerged();
+                       continue;
+               }
+
+               if (!strcmp(arg, "--trivial")) {
+                       trivial_merges_only = 1;
+                       continue;
+               }
+
+               if (!strcmp(arg, "--aggressive")) {
+                       aggressive = 1;
+                       continue;
+               }
+
+               /* "-m" stands for "merge", meaning we start in stage 1 */
+               if (!strcmp(arg, "-m")) {
+                       if (stage || merge)
+                               usage(read_tree_usage);
+                       if (read_cache_unmerged())
+                               die("you need to resolve your current index first");
+                       stage = 1;
+                       merge = 1;
+                       continue;
+               }
+
+               /* using -u and -i at the same time makes no sense */
+               if (1 < index_only + update)
+                       usage(read_tree_usage);
+
+               if (get_sha1(arg, sha1))
+                       die("Not a valid object name %s", arg);
+               if (list_tree(sha1) < 0)
+                       die("failed to unpack tree object %s", arg);
+               stage++;
+       }
+       if ((update||index_only) && !merge)
+               usage(read_tree_usage);
+
+       if (merge) {
+               if (stage < 2)
+                       die("just how do you expect me to merge %d trees?", stage-1);
+               switch (stage - 1) {
+               case 1:
+                       fn = oneway_merge;
+                       break;
+               case 2:
+                       fn = twoway_merge;
+                       break;
+               case 3:
+               default:
+                       fn = threeway_merge;
+                       cache_tree_free(&active_cache_tree);
+                       break;
+               }
+
+               if (stage - 1 >= 3)
+                       head_idx = stage - 2;
+               else
+                       head_idx = 1;
+       }
+
+       unpack_trees(fn);
+
+       /*
+        * When reading only one tree (either the most basic form,
+        * "-m ent" or "--reset ent" form), we can obtain a fully
+        * valid cache-tree because the index must match exactly
+        * what came from the tree.
+        */
+       if (trees && trees->item && (!merge || (stage == 2))) {
+               cache_tree_free(&active_cache_tree);
+               prime_cache_tree();
+       }
+
+       if (write_cache(newfd, active_cache, active_nr) ||
+           commit_lock_file(&lock_file))
+               die("unable to write new index file");
+       return 0;
+}
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
new file mode 100644 (file)
index 0000000..e885624
--- /dev/null
@@ -0,0 +1,362 @@
+#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"
+
+/* bits #0-15 in revision.h */
+
+#define COUNTED                (1u<<16)
+
+static const char rev_list_usage[] =
+"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+"  limiting output:\n"
+"    --max-count=nr\n"
+"    --max-age=epoch\n"
+"    --min-age=epoch\n"
+"    --sparse\n"
+"    --no-merges\n"
+"    --remove-empty\n"
+"    --all\n"
+"  ordering output:\n"
+"    --topo-order\n"
+"    --date-order\n"
+"  formatting output:\n"
+"    --parents\n"
+"    --objects | --objects-edge\n"
+"    --unpacked\n"
+"    --header | --pretty\n"
+"    --abbrev=nr | --no-abbrev\n"
+"    --abbrev-commit\n"
+"  special purpose:\n"
+"    --bisect"
+;
+
+static struct rev_info revs;
+
+static int bisect_list = 0;
+static int show_timestamp = 0;
+static int hdr_termination = 0;
+static const char *header_prefix;
+
+static void show_commit(struct commit *commit)
+{
+       if (show_timestamp)
+               printf("%lu ", commit->date);
+       if (header_prefix)
+               fputs(header_prefix, stdout);
+       if (commit->object.flags & BOUNDARY)
+               putchar('-');
+       if (revs.abbrev_commit && revs.abbrev)
+               fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+                     stdout);
+       else
+               fputs(sha1_to_hex(commit->object.sha1), stdout);
+       if (revs.parents) {
+               struct commit_list *parents = commit->parents;
+               while (parents) {
+                       struct object *o = &(parents->item->object);
+                       parents = parents->next;
+                       if (o->flags & TMP_MARK)
+                               continue;
+                       printf(" %s", sha1_to_hex(o->sha1));
+                       o->flags |= TMP_MARK;
+               }
+               /* TMP_MARK is a general purpose flag that can
+                * be used locally, but the user should clean
+                * things up after it is done with them.
+                */
+               for (parents = commit->parents;
+                    parents;
+                    parents = parents->next)
+                       parents->item->object.flags &= ~TMP_MARK;
+       }
+       if (revs.commit_format == CMIT_FMT_ONELINE)
+               putchar(' ');
+       else
+               putchar('\n');
+
+       if (revs.verbose_header) {
+               static char pretty_header[16384];
+               pretty_print_commit(revs.commit_format, commit, ~0,
+                                   pretty_header, sizeof(pretty_header),
+                                   revs.abbrev, NULL, NULL);
+               printf("%s%c", pretty_header, hdr_termination);
+       }
+       fflush(stdout);
+}
+
+static struct object_list **process_blob(struct blob *blob,
+                                        struct object_list **p,
+                                        struct name_path *path,
+                                        const char *name)
+{
+       struct object *obj = &blob->object;
+
+       if (!revs.blob_objects)
+               return p;
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       obj->flags |= SEEN;
+       name = strdup(name);
+       return add_object(obj, p, path, name);
+}
+
+static struct object_list **process_tree(struct tree *tree,
+                                        struct object_list **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 (!revs.tree_objects)
+               return p;
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+       obj->flags |= SEEN;
+       name = strdup(name);
+       p = 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))
+                       p = process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+               else
+                       p = process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+       return p;
+}
+
+static void show_commit_list(struct rev_info *revs)
+{
+       struct commit *commit;
+       struct object_list *objects = NULL, **p = &objects, *pending;
+
+       while ((commit = get_revision(revs)) != NULL) {
+               p = process_tree(commit->tree, p, NULL, "");
+               show_commit(commit);
+       }
+       for (pending = revs->pending_objects; pending; pending = pending->next) {
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+               if (obj->flags & (UNINTERESTING | SEEN))
+                       continue;
+               if (obj->type == tag_type) {
+                       obj->flags |= SEEN;
+                       p = add_object(obj, p, NULL, name);
+                       continue;
+               }
+               if (obj->type == tree_type) {
+                       p = process_tree((struct tree *)obj, p, NULL, name);
+                       continue;
+               }
+               if (obj->type == blob_type) {
+                       p = process_blob((struct blob *)obj, p, NULL, name);
+                       continue;
+               }
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+       }
+       while (objects) {
+               /* An object with name "foo\n0000000..." can be used to
+                * confuse downstream git-pack-objects very badly.
+                */
+               const char *ep = strchr(objects->name, '\n');
+               if (ep) {
+                       printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
+                              (int) (ep - objects->name),
+                              objects->name);
+               }
+               else
+                       printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
+               objects = objects->next;
+       }
+}
+
+/*
+ * This is a truly stupid algorithm, but it's only
+ * used for bisection, and we just don't care enough.
+ *
+ * We care just barely enough to avoid recursing for
+ * non-merge entries.
+ */
+static int count_distance(struct commit_list *entry)
+{
+       int nr = 0;
+
+       while (entry) {
+               struct commit *commit = entry->item;
+               struct commit_list *p;
+
+               if (commit->object.flags & (UNINTERESTING | COUNTED))
+                       break;
+               if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
+                       nr++;
+               commit->object.flags |= COUNTED;
+               p = commit->parents;
+               entry = p;
+               if (p) {
+                       p = p->next;
+                       while (p) {
+                               nr += count_distance(p);
+                               p = p->next;
+                       }
+               }
+       }
+
+       return nr;
+}
+
+static void clear_distance(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               commit->object.flags &= ~COUNTED;
+               list = list->next;
+       }
+}
+
+static struct commit_list *find_bisection(struct commit_list *list)
+{
+       int nr, closest;
+       struct commit_list *p, *best;
+
+       nr = 0;
+       p = list;
+       while (p) {
+               if (!revs.prune_fn || (p->item->object.flags & TREECHANGE))
+                       nr++;
+               p = p->next;
+       }
+       closest = 0;
+       best = list;
+
+       for (p = list; p; p = p->next) {
+               int distance;
+
+               if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
+                       continue;
+
+               distance = count_distance(p);
+               clear_distance(list);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               if (distance > closest) {
+                       best = p;
+                       closest = distance;
+               }
+       }
+       if (best)
+               best->next = NULL;
+       return best;
+}
+
+static void mark_edge_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents;
+
+       for (parents = commit->parents; parents; parents = parents->next) {
+               struct commit *parent = parents->item;
+               if (!(parent->object.flags & UNINTERESTING))
+                       continue;
+               mark_tree_uninteresting(parent->tree);
+               if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
+                       parent->object.flags |= SHOWN;
+                       printf("-%s\n", sha1_to_hex(parent->object.sha1));
+               }
+       }
+}
+
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+       for ( ; list; list = list->next) {
+               struct commit *commit = list->item;
+
+               if (commit->object.flags & UNINTERESTING) {
+                       mark_tree_uninteresting(commit->tree);
+                       continue;
+               }
+               mark_edge_parents_uninteresting(commit);
+       }
+}
+
+int cmd_rev_list(int argc, const char **argv, char **envp)
+{
+       struct commit_list *list;
+       int i;
+
+       init_revisions(&revs);
+       revs.abbrev = 0;
+       revs.commit_format = CMIT_FMT_UNSPECIFIED;
+       argc = setup_revisions(argc, argv, &revs, NULL);
+
+       for (i = 1 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--header")) {
+                       revs.verbose_header = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--timestamp")) {
+                       show_timestamp = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--bisect")) {
+                       bisect_list = 1;
+                       continue;
+               }
+               usage(rev_list_usage);
+
+       }
+       if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+               /* The command line has a --pretty  */
+               hdr_termination = '\n';
+               if (revs.commit_format == CMIT_FMT_ONELINE)
+                       header_prefix = "";
+               else
+                       header_prefix = "commit ";
+       }
+       else if (revs.verbose_header)
+               /* Only --header was specified */
+               revs.commit_format = CMIT_FMT_RAW;
+
+       list = revs.commits;
+
+       if ((!list &&
+            (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+             !revs.pending_objects)) ||
+           revs.diff)
+               usage(rev_list_usage);
+
+       save_commit_buffer = revs.verbose_header;
+       track_object_refs = 0;
+       if (bisect_list)
+               revs.limited = 1;
+
+       prepare_revision_walk(&revs);
+       if (revs.tree_objects)
+               mark_edges_uninteresting(revs.commits);
+
+       if (bisect_list)
+               revs.commits = find_bisection(revs.commits);
+
+       show_commit_list(&revs);
+
+       return 0;
+}
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
new file mode 100644 (file)
index 0000000..b27a6d3
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "quote.h"
+#include "builtin.h"
+
+#define DO_REVS                1
+#define DO_NOREV       2
+#define DO_FLAGS       4
+#define DO_NONFLAGS    8
+static int filter = ~0;
+
+static const char *def = NULL;
+
+#define NORMAL 0
+#define REVERSED 1
+static int show_type = NORMAL;
+static int symbolic = 0;
+static int abbrev = 0;
+static int output_sq = 0;
+
+static int revs_count = 0;
+
+/*
+ * Some arguments are relevant "revision" arguments,
+ * others are about output format or other details.
+ * This sorts it all out.
+ */
+static int is_rev_argument(const char *arg)
+{
+       static const char *rev_args[] = {
+               "--all",
+               "--bisect",
+               "--dense",
+               "--branches",
+               "--header",
+               "--max-age=",
+               "--max-count=",
+               "--min-age=",
+               "--no-merges",
+               "--objects",
+               "--objects-edge",
+               "--parents",
+               "--pretty",
+               "--remotes",
+               "--sparse",
+               "--tags",
+               "--topo-order",
+               "--date-order",
+               "--unpacked",
+               NULL
+       };
+       const char **p = rev_args;
+
+       /* accept -<digit>, like traditional "head" */
+       if ((*arg == '-') && isdigit(arg[1]))
+               return 1;
+
+       for (;;) {
+               const char *str = *p++;
+               int len;
+               if (!str)
+                       return 0;
+               len = strlen(str);
+               if (!strcmp(arg, str) ||
+                   (str[len-1] == '=' && !strncmp(arg, str, len)))
+                       return 1;
+       }
+}
+
+/* Output argument as a string, either SQ or normal */
+static void show(const char *arg)
+{
+       if (output_sq) {
+               int sq = '\'', ch;
+
+               putchar(sq);
+               while ((ch = *arg++)) {
+                       if (ch == sq)
+                               fputs("'\\'", stdout);
+                       putchar(ch);
+               }
+               putchar(sq);
+               putchar(' ');
+       }
+       else
+               puts(arg);
+}
+
+/* Output a revision, only if filter allows it */
+static void show_rev(int type, const unsigned char *sha1, const char *name)
+{
+       if (!(filter & DO_REVS))
+               return;
+       def = NULL;
+       revs_count++;
+
+       if (type != show_type)
+               putchar('^');
+       if (symbolic && name)
+               show(name);
+       else if (abbrev)
+               show(find_unique_abbrev(sha1, abbrev));
+       else
+               show(sha1_to_hex(sha1));
+}
+
+/* Output a flag, only if filter allows it. */
+static int show_flag(const char *arg)
+{
+       if (!(filter & DO_FLAGS))
+               return 0;
+       if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV)) {
+               show(arg);
+               return 1;
+       }
+       return 0;
+}
+
+static void show_default(void)
+{
+       const char *s = def;
+
+       if (s) {
+               unsigned char sha1[20];
+
+               def = NULL;
+               if (!get_sha1(s, sha1)) {
+                       show_rev(NORMAL, sha1, s);
+                       return;
+               }
+       }
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+       show_rev(NORMAL, sha1, refname);
+       return 0;
+}
+
+static void show_datestring(const char *flag, const char *datestr)
+{
+       static char buffer[100];
+
+       /* date handling requires both flags and revs */
+       if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
+               return;
+       snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
+       show(buffer);
+}
+
+static int show_file(const char *arg)
+{
+       show_default();
+       if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) {
+               show(arg);
+               return 1;
+       }
+       return 0;
+}
+
+int cmd_rev_parse(int argc, const char **argv, char **envp)
+{
+       int i, as_is = 0, verify = 0;
+       unsigned char sha1[20];
+       const char *prefix = setup_git_directory();
+
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               char *dotdot;
+
+               if (as_is) {
+                       if (show_file(arg) && as_is < 2)
+                               verify_filename(prefix, arg);
+                       continue;
+               }
+               if (!strcmp(arg,"-n")) {
+                       if (++i >= argc)
+                               die("-n requires an argument");
+                       if ((filter & DO_FLAGS) && (filter & DO_REVS)) {
+                               show(arg);
+                               show(argv[i]);
+                       }
+                       continue;
+               }
+               if (!strncmp(arg,"-n",2)) {
+                       if ((filter & DO_FLAGS) && (filter & DO_REVS))
+                               show(arg);
+                       continue;
+               }
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               as_is = 2;
+                               /* Pass on the "--" if we show anything but files.. */
+                               if (filter & (DO_FLAGS | DO_REVS))
+                                       show_file(arg);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               def = argv[i+1];
+                               i++;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--revs-only")) {
+                               filter &= ~DO_NOREV;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-revs")) {
+                               filter &= ~DO_REVS;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--flags")) {
+                               filter &= ~DO_NONFLAGS;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-flags")) {
+                               filter &= ~DO_FLAGS;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verify")) {
+                               filter &= ~(DO_FLAGS|DO_NOREV);
+                               verify = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--short") ||
+                           !strncmp(arg, "--short=", 8)) {
+                               filter &= ~(DO_FLAGS|DO_NOREV);
+                               verify = 1;
+                               abbrev = DEFAULT_ABBREV;
+                               if (arg[7] == '=')
+                                       abbrev = strtoul(arg + 8, NULL, 10);
+                               if (abbrev < MINIMUM_ABBREV)
+                                       abbrev = MINIMUM_ABBREV;
+                               else if (40 <= abbrev)
+                                       abbrev = 40;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--sq")) {
+                               output_sq = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--not")) {
+                               show_type ^= REVERSED;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--symbolic")) {
+                               symbolic = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               for_each_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--branches")) {
+                               for_each_branch_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--tags")) {
+                               for_each_tag_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remotes")) {
+                               for_each_remote_ref(show_reference);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--show-prefix")) {
+                               if (prefix)
+                                       puts(prefix);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--show-cdup")) {
+                               const char *pfx = prefix;
+                               while (pfx) {
+                                       pfx = strchr(pfx, '/');
+                                       if (pfx) {
+                                               pfx++;
+                                               printf("../");
+                                       }
+                               }
+                               putchar('\n');
+                               continue;
+                       }
+                       if (!strcmp(arg, "--git-dir")) {
+                               const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
+                               static char cwd[PATH_MAX];
+                               if (gitdir) {
+                                       puts(gitdir);
+                                       continue;
+                               }
+                               if (!prefix) {
+                                       puts(".git");
+                                       continue;
+                               }
+                               if (!getcwd(cwd, PATH_MAX))
+                                       die("unable to get current working directory");
+                               printf("%s/.git\n", cwd);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--since=", 8)) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--after=", 8)) {
+                               show_datestring("--max-age=", arg+8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--before=", 9)) {
+                               show_datestring("--min-age=", arg+9);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--until=", 8)) {
+                               show_datestring("--min-age=", arg+8);
+                               continue;
+                       }
+                       if (show_flag(arg) && verify)
+                               die("Needed a single revision");
+                       continue;
+               }
+
+               /* Not a flag argument */
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char end[20];
+                       char *next = dotdot + 2;
+                       const char *this = arg;
+                       *dotdot = 0;
+                       if (!*next)
+                               next = "HEAD";
+                       if (dotdot == arg)
+                               this = "HEAD";
+                       if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
+                               show_rev(NORMAL, end, next);
+                               show_rev(REVERSED, sha1, this);
+                               continue;
+                       }
+                       *dotdot = '.';
+               }
+               if (!get_sha1(arg, sha1)) {
+                       show_rev(NORMAL, sha1, arg);
+                       continue;
+               }
+               if (*arg == '^' && !get_sha1(arg+1, sha1)) {
+                       show_rev(REVERSED, sha1, arg+1);
+                       continue;
+               }
+               as_is = 1;
+               if (!show_file(arg))
+                       continue;
+               if (verify)
+                       die("Needed a single revision");
+               verify_filename(prefix, arg);
+       }
+       show_default();
+       if (verify && revs_count != 1)
+               die("Needed a single revision");
+       return 0;
+}
diff --git a/builtin-rm.c b/builtin-rm.c
new file mode 100644 (file)
index 0000000..ef2f8b5
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * "git rm" builtin command
+ *
+ * Copyright (C) Linus Torvalds 2006
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+#include "cache-tree.h"
+
+static const char builtin_rm_usage[] =
+"git-rm [-n] [-v] [-f] <filepattern>...";
+
+static struct {
+       int nr, alloc;
+       const char **name;
+} list;
+
+static void add_list(const char *name)
+{
+       if (list.nr >= list.alloc) {
+               list.alloc = alloc_nr(list.alloc);
+               list.name = xrealloc(list.name, list.alloc * sizeof(const char *));
+       }
+       list.name[list.nr++] = name;
+}
+
+static int remove_file(const char *name)
+{
+       int ret;
+       char *slash;
+
+       ret = unlink(name);
+       if (!ret && (slash = strrchr(name, '/'))) {
+               char *n = strdup(name);
+               do {
+                       n[slash - name] = 0;
+                       name = n;
+               } while (!rmdir(name) && (slash = strrchr(name, '/')));
+       }
+       return ret;
+}
+
+static struct lock_file lock_file;
+
+int cmd_rm(int argc, const char **argv, char **envp)
+{
+       int i, newfd;
+       int verbose = 0, show_only = 0, force = 0;
+       const char *prefix = setup_git_directory();
+       const char **pathspec;
+       char *seen;
+
+       git_config(git_default_config);
+
+       newfd = hold_lock_file_for_update(&lock_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       for (i = 1 ; i < argc ; i++) {
+               const char *arg = argv[i];
+
+               if (*arg != '-')
+                       break;
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-n")) {
+                       show_only = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force = 1;
+                       continue;
+               }
+               die(builtin_rm_usage);
+       }
+       pathspec = get_pathspec(prefix, argv + i);
+
+       seen = NULL;
+       if (pathspec) {
+               for (i = 0; pathspec[i] ; i++)
+                       /* nothing */;
+               seen = xmalloc(i);
+               memset(seen, 0, i);
+       }
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+                       continue;
+               add_list(ce->name);
+       }
+
+       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);
+               }
+       }
+
+       /*
+        * First remove the names from the index: we won't commit
+        * the index unless all of them succeed
+        */
+       for (i = 0; i < list.nr; i++) {
+               const char *path = list.name[i];
+               printf("rm '%s'\n", path);
+
+               if (remove_file_from_cache(path))
+                       die("git rm: unable to remove %s", path);
+               cache_tree_invalidate_path(active_cache_tree, path);
+       }
+
+       /*
+        * Then, if we used "-f", 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 ourself and can't fail
+        * in the middle)
+        */
+       if (force) {
+               int removed = 0;
+               for (i = 0; i < list.nr; i++) {
+                       const char *path = list.name[i];
+                       if (!remove_file(path)) {
+                               removed = 1;
+                               continue;
+                       }
+                       if (!removed)
+                               die("git rm: %s: %s", path, strerror(errno));
+               }
+       }
+
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_lock_file(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return 0;
+}
diff --git a/builtin-show-branch.c b/builtin-show-branch.c
new file mode 100644 (file)
index 0000000..2895140
--- /dev/null
@@ -0,0 +1,789 @@
+#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 [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+
+static int default_num = 0;
+static int default_alloc = 0;
+static const char **default_arg = NULL;
+
+#define UNINTERESTING  01
+
+#define REV_SHIFT       2
+#define MAX_REVS       29 /* should not exceed bits_per_int - REV_SHIFT */
+
+static struct commit *interesting(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return commit;
+       }
+       return NULL;
+}
+
+static struct commit *pop_one_commit(struct commit_list **list_p)
+{
+       struct commit *commit;
+       struct commit_list *list;
+       list = *list_p;
+       commit = list->item;
+       *list_p = list->next;
+       free(list);
+       return commit;
+}
+
+struct commit_name {
+       const char *head_name; /* which head's ancestor? */
+       int generation; /* how many parents away from head_name */
+};
+
+/* Name the commit as nth generation ancestor of head_name;
+ * we count only the first-parent relationship for naming purposes.
+ */
+static void name_commit(struct commit *commit, const char *head_name, int nth)
+{
+       struct commit_name *name;
+       if (!commit->object.util)
+               commit->object.util = xmalloc(sizeof(struct commit_name));
+       name = commit->object.util;
+       name->head_name = head_name;
+       name->generation = nth;
+}
+
+/* Parent is the first parent of the commit.  We may name it
+ * as (n+1)th generation ancestor of the same head_name as
+ * commit is nth generation ancestor of, if that generation
+ * number is better than the name it already has.
+ */
+static void name_parent(struct commit *commit, struct commit *parent)
+{
+       struct commit_name *commit_name = commit->object.util;
+       struct commit_name *parent_name = parent->object.util;
+       if (!commit_name)
+               return;
+       if (!parent_name ||
+           commit_name->generation + 1 < parent_name->generation)
+               name_commit(parent, commit_name->head_name,
+                           commit_name->generation + 1);
+}
+
+static int name_first_parent_chain(struct commit *c)
+{
+       int i = 0;
+       while (c) {
+               struct commit *p;
+               if (!c->object.util)
+                       break;
+               if (!c->parents)
+                       break;
+               p = c->parents->item;
+               if (!p->object.util) {
+                       name_parent(c, p);
+                       i++;
+               }
+               c = p;
+       }
+       return i;
+}
+
+static void name_commits(struct commit_list *list,
+                        struct commit **rev,
+                        char **ref_name,
+                        int num_rev)
+{
+       struct commit_list *cl;
+       struct commit *c;
+       int i;
+
+       /* First give names to the given heads */
+       for (cl = list; cl; cl = cl->next) {
+               c = cl->item;
+               if (c->object.util)
+                       continue;
+               for (i = 0; i < num_rev; i++) {
+                       if (rev[i] == c) {
+                               name_commit(c, ref_name[i], 0);
+                               break;
+                       }
+               }
+       }
+
+       /* Then commits on the first parent ancestry chain */
+       do {
+               i = 0;
+               for (cl = list; cl; cl = cl->next) {
+                       i += name_first_parent_chain(cl->item);
+               }
+       } while (i);
+
+       /* Finally, any unnamed commits */
+       do {
+               i = 0;
+               for (cl = list; cl; cl = cl->next) {
+                       struct commit_list *parents;
+                       struct commit_name *n;
+                       int nth;
+                       c = cl->item;
+                       if (!c->object.util)
+                               continue;
+                       n = c->object.util;
+                       parents = c->parents;
+                       nth = 0;
+                       while (parents) {
+                               struct commit *p = parents->item;
+                               char newname[1000], *en;
+                               parents = parents->next;
+                               nth++;
+                               if (p->object.util)
+                                       continue;
+                               en = newname;
+                               switch (n->generation) {
+                               case 0:
+                                       en += sprintf(en, "%s", n->head_name);
+                                       break;
+                               case 1:
+                                       en += sprintf(en, "%s^", n->head_name);
+                                       break;
+                               default:
+                                       en += sprintf(en, "%s~%d",
+                                               n->head_name, n->generation);
+                                       break;
+                               }
+                               if (nth == 1)
+                                       en += sprintf(en, "^");
+                               else
+                                       en += sprintf(en, "^%d", nth);
+                               name_commit(p, strdup(newname), 0);
+                               i++;
+                               name_first_parent_chain(p);
+                       }
+               }
+       } while (i);
+}
+
+static int mark_seen(struct commit *commit, struct commit_list **seen_p)
+{
+       if (!commit->object.flags) {
+               insert_by_date(commit, seen_p);
+               return 1;
+       }
+       return 0;
+}
+
+static void join_revs(struct commit_list **list_p,
+                     struct commit_list **seen_p,
+                     int num_rev, int extra)
+{
+       int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+       int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+       while (*list_p) {
+               struct commit_list *parents;
+               int still_interesting = !!interesting(*list_p);
+               struct commit *commit = pop_one_commit(list_p);
+               int flags = commit->object.flags & all_mask;
+
+               if (!still_interesting && extra <= 0)
+                       break;
+
+               mark_seen(commit, seen_p);
+               if ((flags & all_revs) == all_revs)
+                       flags |= UNINTERESTING;
+               parents = commit->parents;
+
+               while (parents) {
+                       struct commit *p = parents->item;
+                       int this_flag = p->object.flags;
+                       parents = parents->next;
+                       if ((this_flag & flags) == flags)
+                               continue;
+                       if (!p->object.parsed)
+                               parse_commit(p);
+                       if (mark_seen(p, seen_p) && !still_interesting)
+                               extra--;
+                       p->object.flags |= flags;
+                       insert_by_date(p, list_p);
+               }
+       }
+
+       /*
+        * Postprocess to complete well-poisoning.
+        *
+        * At this point we have all the commits we have seen in
+        * seen_p list (which happens to be sorted chronologically but
+        * it does not really matter).  Mark anything that can be
+        * reached from uninteresting commits not interesting.
+        */
+       for (;;) {
+               int changed = 0;
+               struct commit_list *s;
+               for (s = *seen_p; s; s = s->next) {
+                       struct commit *c = s->item;
+                       struct commit_list *parents;
+
+                       if (((c->object.flags & all_revs) != all_revs) &&
+                           !(c->object.flags & UNINTERESTING))
+                               continue;
+
+                       /* The current commit is either a merge base or
+                        * already uninteresting one.  Mark its parents
+                        * as uninteresting commits _only_ if they are
+                        * already parsed.  No reason to find new ones
+                        * here.
+                        */
+                       parents = c->parents;
+                       while (parents) {
+                               struct commit *p = parents->item;
+                               parents = parents->next;
+                               if (!(p->object.flags & UNINTERESTING)) {
+                                       p->object.flags |= UNINTERESTING;
+                                       changed = 1;
+                               }
+                       }
+               }
+               if (!changed)
+                       break;
+       }
+}
+
+static void show_one_commit(struct commit *commit, int no_name)
+{
+       char pretty[256], *cp;
+       struct commit_name *name = commit->object.util;
+       if (commit->object.parsed)
+               pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+                                   pretty, sizeof(pretty), 0, NULL, NULL);
+       else
+               strcpy(pretty, "(unavailable)");
+       if (!strncmp(pretty, "[PATCH] ", 8))
+               cp = pretty + 8;
+       else
+               cp = pretty;
+
+       if (!no_name) {
+               if (name && name->head_name) {
+                       printf("[%s", name->head_name);
+                       if (name->generation) {
+                               if (name->generation == 1)
+                                       printf("^");
+                               else
+                                       printf("~%d", name->generation);
+                       }
+                       printf("] ");
+               }
+               else
+                       printf("[%s] ",
+                              find_unique_abbrev(commit->object.sha1, 7));
+       }
+       puts(cp);
+}
+
+static char *ref_name[MAX_REVS + 1];
+static int ref_name_cnt;
+
+static const char *find_digit_prefix(const char *s, int *v)
+{
+       const char *p;
+       int ver;
+       char ch;
+
+       for (p = s, ver = 0;
+            '0' <= (ch = *p) && ch <= '9';
+            p++)
+               ver = ver * 10 + ch - '0';
+       *v = ver;
+       return p;
+}
+
+
+static int version_cmp(const char *a, const char *b)
+{
+       while (1) {
+               int va, vb;
+
+               a = find_digit_prefix(a, &va);
+               b = find_digit_prefix(b, &vb);
+               if (va != vb)
+                       return va - vb;
+
+               while (1) {
+                       int ca = *a;
+                       int cb = *b;
+                       if ('0' <= ca && ca <= '9')
+                               ca = 0;
+                       if ('0' <= cb && cb <= '9')
+                               cb = 0;
+                       if (ca != cb)
+                               return ca - cb;
+                       if (!ca)
+                               break;
+                       a++;
+                       b++;
+               }
+               if (!*a && !*b)
+                       return 0;
+       }
+}
+
+static int compare_ref_name(const void *a_, const void *b_)
+{
+       const char * const*a = a_, * const*b = b_;
+       return version_cmp(*a, *b);
+}
+
+static void sort_ref_range(int bottom, int top)
+{
+       qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
+             compare_ref_name);
+}
+
+static int append_ref(const char *refname, const unsigned char *sha1)
+{
+       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 (MAX_REVS <= ref_name_cnt) {
+               fprintf(stderr, "warning: ignoring %s; "
+                       "cannot handle more than %d refs\n",
+                       refname, MAX_REVS);
+               return 0;
+       }
+       ref_name[ref_name_cnt++] = strdup(refname);
+       ref_name[ref_name_cnt] = NULL;
+       return 0;
+}
+
+static int append_head_ref(const char *refname, const unsigned char *sha1)
+{
+       unsigned char tmp[20];
+       int ofs = 11;
+       if (strncmp(refname, "refs/heads/", ofs))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1);
+}
+
+static int append_tag_ref(const char *refname, const unsigned char *sha1)
+{
+       if (strncmp(refname, "refs/tags/", 10))
+               return 0;
+       return append_ref(refname + 5, sha1);
+}
+
+static const char *match_ref_pattern = NULL;
+static int match_ref_slash = 0;
+static int count_slash(const char *s)
+{
+       int cnt = 0;
+       while (*s)
+               if (*s++ == '/')
+                       cnt++;
+       return cnt;
+}
+
+static int append_matching_ref(const char *refname, const unsigned char *sha1)
+{
+       /* we want to allow pattern hold/<asterisk> to show all
+        * branches under refs/heads/hold/, and v0.99.9? to show
+        * refs/tags/v0.99.9a and friends.
+        */
+       const char *tail;
+       int slash = count_slash(refname);
+       for (tail = refname; *tail && match_ref_slash < slash; )
+               if (*tail++ == '/')
+                       slash--;
+       if (!*tail)
+               return 0;
+       if (fnmatch(match_ref_pattern, tail, 0))
+               return 0;
+       if (!strncmp("refs/heads/", refname, 11))
+               return append_head_ref(refname, sha1);
+       if (!strncmp("refs/tags/", refname, 10))
+               return append_tag_ref(refname, sha1);
+       return append_ref(refname, sha1);
+}
+
+static void snarf_refs(int head, int tag)
+{
+       if (head) {
+               int orig_cnt = ref_name_cnt;
+               for_each_ref(append_head_ref);
+               sort_ref_range(orig_cnt, ref_name_cnt);
+       }
+       if (tag) {
+               int orig_cnt = ref_name_cnt;
+               for_each_ref(append_tag_ref);
+               sort_ref_range(orig_cnt, ref_name_cnt);
+       }
+}
+
+static int rev_is_head(char *head_path, int headlen, char *name,
+                      unsigned char *head_sha1, unsigned char *sha1)
+{
+       int namelen;
+       if ((!head_path[0]) ||
+           (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20)))
+               return 0;
+       namelen = strlen(name);
+       if ((headlen < namelen) ||
+           memcmp(head_path + headlen - namelen, name, namelen))
+               return 0;
+       if (headlen == namelen ||
+           head_path[headlen - namelen - 1] == '/')
+               return 1;
+       return 0;
+}
+
+static int show_merge_base(struct commit_list *seen, int num_rev)
+{
+       int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+       int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+       int exit_status = 1;
+
+       while (seen) {
+               struct commit *commit = pop_one_commit(&seen);
+               int flags = commit->object.flags & all_mask;
+               if (!(flags & UNINTERESTING) &&
+                   ((flags & all_revs) == all_revs)) {
+                       puts(sha1_to_hex(commit->object.sha1));
+                       exit_status = 0;
+                       commit->object.flags |= UNINTERESTING;
+               }
+       }
+       return exit_status;
+}
+
+static int show_independent(struct commit **rev,
+                           int num_rev,
+                           char **ref_name,
+                           unsigned int *rev_mask)
+{
+       int i;
+
+       for (i = 0; i < num_rev; i++) {
+               struct commit *commit = rev[i];
+               unsigned int flag = rev_mask[i];
+
+               if (commit->object.flags == flag)
+                       puts(sha1_to_hex(commit->object.sha1));
+               commit->object.flags |= UNINTERESTING;
+       }
+       return 0;
+}
+
+static void append_one_rev(const char *av)
+{
+       unsigned char revkey[20];
+       if (!get_sha1(av, revkey)) {
+               append_ref(av, revkey);
+               return;
+       }
+       if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
+               /* glob style match */
+               int saved_matches = ref_name_cnt;
+               match_ref_pattern = av;
+               match_ref_slash = count_slash(av);
+               for_each_ref(append_matching_ref);
+               if (saved_matches == ref_name_cnt &&
+                   ref_name_cnt < MAX_REVS)
+                       error("no matching refs with %s", av);
+               if (saved_matches + 1 < ref_name_cnt)
+                       sort_ref_range(saved_matches, ref_name_cnt);
+               return;
+       }
+       die("bad sha1 reference %s", av);
+}
+
+static int git_show_branch_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "showbranch.default")) {
+               if (default_alloc <= default_num + 1) {
+                       default_alloc = default_alloc * 3 / 2 + 20;
+                       default_arg = xrealloc(default_arg, sizeof *default_arg * default_alloc);
+               }
+               default_arg[default_num++] = strdup(value);
+               default_arg[default_num] = NULL;
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+{
+       /* If the commit is tip of the named branches, do not
+        * omit it.
+        * Otherwise, if it is a merge that is reachable from only one
+        * tip, it is not that interesting.
+        */
+       int i, flag, count;
+       for (i = 0; i < n; i++)
+               if (rev[i] == commit)
+                       return 0;
+       flag = commit->object.flags;
+       for (i = count = 0; i < n; i++) {
+               if (flag & (1u << (i + REV_SHIFT)))
+                       count++;
+       }
+       if (count == 1)
+               return 1;
+       return 0;
+}
+
+int cmd_show_branch(int ac, const char **av, char **envp)
+{
+       struct commit *rev[MAX_REVS], *commit;
+       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_mask, all_revs;
+       int lifo = 1;
+       char head_path[128];
+       const char *head_path_p;
+       int head_path_len;
+       unsigned char head_sha1[20];
+       int merge_base = 0;
+       int independent = 0;
+       int no_name = 0;
+       int sha1_name = 0;
+       int shown_merge_point = 0;
+       int with_current_branch = 0;
+       int head_at = -1;
+       int topics = 0;
+       int dense = 1;
+
+       setup_git_directory();
+       git_config(git_show_branch_config);
+
+       /* If nothing is specified, try the default first */
+       if (ac == 1 && default_num) {
+               ac = default_num + 1;
+               av = default_arg - 1; /* ick; we would not address av[0] */
+       }
+
+       while (1 < ac && av[1][0] == '-') {
+               const char *arg = av[1];
+               if (!strcmp(arg, "--")) {
+                       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, "--more"))
+                       extra = 1;
+               else if (!strcmp(arg, "--list"))
+                       extra = -1;
+               else if (!strcmp(arg, "--no-name"))
+                       no_name = 1;
+               else if (!strcmp(arg, "--current"))
+                       with_current_branch = 1;
+               else if (!strcmp(arg, "--sha1-name"))
+                       sha1_name = 1;
+               else if (!strncmp(arg, "--more=", 7))
+                       extra = atoi(arg + 7);
+               else if (!strcmp(arg, "--merge-base"))
+                       merge_base = 1;
+               else if (!strcmp(arg, "--independent"))
+                       independent = 1;
+               else if (!strcmp(arg, "--topo-order"))
+                       lifo = 1;
+               else if (!strcmp(arg, "--topics"))
+                       topics = 1;
+               else if (!strcmp(arg, "--sparse"))
+                       dense = 0;
+               else if (!strcmp(arg, "--date-order"))
+                       lifo = 0;
+               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 nothing is specified, show all branches by default */
+       if (ac + all_heads + all_tags == 0)
+               all_heads = 1;
+
+       if (all_heads + all_tags)
+               snarf_refs(all_heads, all_tags);
+       while (0 < ac) {
+               append_one_rev(*av);
+               ac--; av++;
+       }
+
+       head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+       if (head_path_p) {
+               head_path_len = strlen(head_path_p);
+               memcpy(head_path, head_path_p, head_path_len + 1);
+       }
+       else {
+               head_path_len = 0;
+               head_path[0] = 0;
+       }
+
+       if (with_current_branch && head_path_p) {
+               int has_head = 0;
+               for (i = 0; !has_head && i < ref_name_cnt; i++) {
+                       /* We are only interested in adding the branch
+                        * HEAD points at.
+                        */
+                       if (rev_is_head(head_path,
+                                       head_path_len,
+                                       ref_name[i],
+                                       head_sha1, NULL))
+                               has_head++;
+               }
+               if (!has_head) {
+                       int pfxlen = strlen(git_path("refs/heads/"));
+                       append_one_rev(head_path + pfxlen);
+               }
+       }
+
+       if (!ref_name_cnt) {
+               fprintf(stderr, "No revs to be shown.\n");
+               exit(0);
+       }
+
+       for (num_rev = 0; ref_name[num_rev]; num_rev++) {
+               unsigned char revkey[20];
+               unsigned int flag = 1u << (num_rev + REV_SHIFT);
+
+               if (MAX_REVS <= num_rev)
+                       die("cannot handle more than %d revs.", MAX_REVS);
+               if (get_sha1(ref_name[num_rev], revkey))
+                       die("'%s' is not a valid ref.", ref_name[num_rev]);
+               commit = lookup_commit_reference(revkey);
+               if (!commit)
+                       die("cannot find commit %s (%s)",
+                           ref_name[num_rev], revkey);
+               parse_commit(commit);
+               mark_seen(commit, &seen);
+
+               /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
+                * and so on.  REV_SHIFT bits from bit 0 are used for
+                * internal bookkeeping.
+                */
+               commit->object.flags |= flag;
+               if (commit->object.flags == flag)
+                       insert_by_date(commit, &list);
+               rev[num_rev] = commit;
+       }
+       for (i = 0; i < num_rev; i++)
+               rev_mask[i] = rev[i]->object.flags;
+
+       if (0 <= extra)
+               join_revs(&list, &seen, num_rev, extra);
+
+       if (merge_base)
+               return show_merge_base(seen, num_rev);
+
+       if (independent)
+               return show_independent(rev, num_rev, ref_name, rev_mask);
+
+       /* Show list; --more=-1 means list-only */
+       if (1 < num_rev || extra < 0) {
+               for (i = 0; i < num_rev; i++) {
+                       int j;
+                       int is_head = rev_is_head(head_path,
+                                                 head_path_len,
+                                                 ref_name[i],
+                                                 head_sha1,
+                                                 rev[i]->object.sha1);
+                       if (extra < 0)
+                               printf("%c [%s] ",
+                                      is_head ? '*' : ' ', ref_name[i]);
+                       else {
+                               for (j = 0; j < i; j++)
+                                       putchar(' ');
+                               printf("%c [%s] ",
+                                      is_head ? '*' : '!', ref_name[i]);
+                       }
+                       /* header lines never need name */
+                       show_one_commit(rev[i], 1);
+                       if (is_head)
+                               head_at = i;
+               }
+               if (0 <= extra) {
+                       for (i = 0; i < num_rev; i++)
+                               putchar('-');
+                       putchar('\n');
+               }
+       }
+       if (extra < 0)
+               exit(0);
+
+       /* Sort topologically */
+       sort_in_topological_order(&seen, lifo);
+
+       /* Give names to commits */
+       if (!sha1_name && !no_name)
+               name_commits(seen, rev, ref_name, num_rev);
+
+       all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+       all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+       while (seen) {
+               struct commit *commit = pop_one_commit(&seen);
+               int this_flag = commit->object.flags;
+               int is_merge_point = ((this_flag & all_revs) == all_revs);
+
+               shown_merge_point |= is_merge_point;
+
+               if (1 < num_rev) {
+                       int is_merge = !!(commit->parents &&
+                                         commit->parents->next);
+                       if (topics &&
+                           !is_merge_point &&
+                           (this_flag & (1u << REV_SHIFT)))
+                               continue;
+                       if (dense && is_merge &&
+                           omit_in_dense(commit, rev, num_rev))
+                               continue;
+                       for (i = 0; i < num_rev; i++) {
+                               int mark;
+                               if (!(this_flag & (1u << (i + REV_SHIFT))))
+                                       mark = ' ';
+                               else if (is_merge)
+                                       mark = '-';
+                               else if (i == head_at)
+                                       mark = '*';
+                               else
+                                       mark = '+';
+                               putchar(mark);
+                       }
+                       putchar(' ');
+               }
+               show_one_commit(commit, no_name);
+
+               if (shown_merge_point && --extra < 0)
+                       break;
+       }
+       return 0;
+}
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
new file mode 100644 (file)
index 0000000..5f740cf
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2005, 2006 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+#include "tree-walk.h"
+#include "commit.h"
+#include "strbuf.h"
+#include "tar.h"
+#include "builtin.h"
+#include "pkt-line.h"
+
+#define RECORDSIZE     (512)
+#define BLOCKSIZE      (RECORDSIZE * 20)
+
+static const char tar_tree_usage[] =
+"git-tar-tree [--remote=<repo>] <ent> [basedir]";
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static time_t archive_time;
+
+/* tries hard to write, either succeeds or dies in the attempt */
+static void reliable_write(void *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       if (errno == EPIPE)
+                               exit(0);
+                       die("git-tar-tree: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-tar-tree: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+       if (offset == BLOCKSIZE) {
+               reliable_write(block, BLOCKSIZE);
+               offset = 0;
+       }
+}
+
+/* acquire the next record from the buffer; user must call write_if_needed() */
+static char *get_record(void)
+{
+       char *p = block + offset;
+       memset(p, 0, RECORDSIZE);
+       offset += RECORDSIZE;
+       return p;
+}
+
+/*
+ * The end of tar archives is marked by 1024 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+       get_record();
+       write_if_needed();
+       get_record();
+       write_if_needed();
+       while (offset) {
+               get_record();
+               write_if_needed();
+       }
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(void *buf, unsigned long size)
+{
+       unsigned long tail;
+
+       if (offset) {
+               unsigned long chunk = BLOCKSIZE - offset;
+               if (size < chunk)
+                       chunk = size;
+               memcpy(block + offset, buf, chunk);
+               size -= chunk;
+               offset += chunk;
+               buf += chunk;
+               write_if_needed();
+       }
+       while (size >= BLOCKSIZE) {
+               reliable_write(buf, BLOCKSIZE);
+               size -= BLOCKSIZE;
+               buf += BLOCKSIZE;
+       }
+       if (size) {
+               memcpy(block + offset, buf, size);
+               offset += size;
+       }
+       tail = offset % RECORDSIZE;
+       if (tail)  {
+               memset(block + offset, 0, RECORDSIZE - tail);
+               offset += RECORDSIZE - tail;
+       }
+       write_if_needed();
+}
+
+static void strbuf_append_string(struct strbuf *sb, const char *s)
+{
+       int slen = strlen(s);
+       int total = sb->len + slen;
+       if (total > sb->alloc) {
+               sb->buf = xrealloc(sb->buf, total);
+               sb->alloc = total;
+       }
+       memcpy(sb->buf + sb->len, s, slen);
+       sb->len = total;
+}
+
+/*
+ * pax extended header records have the format "%u %s=%s\n".  %u contains
+ * the size of the whole string (including the %u), the first %s is the
+ * keyword, the second one is the value.  This function constructs such a
+ * string and appends it to a struct strbuf.
+ */
+static void strbuf_append_ext_header(struct strbuf *sb, const char *keyword,
+                                     const char *value, unsigned int valuelen)
+{
+       char *p;
+       int len, total, tmp;
+
+       /* "%u %s=%s\n" */
+       len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+       for (tmp = len; tmp > 9; tmp /= 10)
+               len++;
+
+       total = sb->len + len;
+       if (total > sb->alloc) {
+               sb->buf = xrealloc(sb->buf, total);
+               sb->alloc = total;
+       }
+
+       p = sb->buf;
+       p += sprintf(p, "%u %s=", len, keyword);
+       memcpy(p, value, valuelen);
+       p += valuelen;
+       *p = '\n';
+       sb->len = total;
+}
+
+static unsigned int ustar_header_chksum(const struct ustar_header *header)
+{
+       char *p = (char *)header;
+       unsigned int chksum = 0;
+       while (p < header->chksum)
+               chksum += *p++;
+       chksum += sizeof(header->chksum) * ' ';
+       p += sizeof(header->chksum);
+       while (p < (char *)header + sizeof(struct ustar_header))
+               chksum += *p++;
+       return chksum;
+}
+
+static int get_path_prefix(const struct strbuf *path, int maxlen)
+{
+       int i = path->len;
+       if (i > maxlen)
+               i = maxlen;
+       while (i > 0 && path->buf[i] != '/')
+               i--;
+       return i;
+}
+
+static void write_entry(const unsigned char *sha1, struct strbuf *path,
+                        unsigned int mode, void *buffer, unsigned long size)
+{
+       struct ustar_header header;
+       struct strbuf ext_header;
+
+       memset(&header, 0, sizeof(header));
+       ext_header.buf = NULL;
+       ext_header.len = ext_header.alloc = 0;
+
+       if (!sha1) {
+               *header.typeflag = TYPEFLAG_GLOBAL_HEADER;
+               mode = 0100666;
+               strcpy(header.name, "pax_global_header");
+       } else if (!path) {
+               *header.typeflag = TYPEFLAG_EXT_HEADER;
+               mode = 0100666;
+               sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
+       } else {
+               if (S_ISDIR(mode)) {
+                       *header.typeflag = TYPEFLAG_DIR;
+                       mode |= 0777;
+               } else if (S_ISLNK(mode)) {
+                       *header.typeflag = TYPEFLAG_LNK;
+                       mode |= 0777;
+               } else if (S_ISREG(mode)) {
+                       *header.typeflag = TYPEFLAG_REG;
+                       mode |= (mode & 0100) ? 0777 : 0666;
+               } else {
+                       error("unsupported file mode: 0%o (SHA1: %s)",
+                             mode, sha1_to_hex(sha1));
+                       return;
+               }
+               if (path->len > sizeof(header.name)) {
+                       int plen = get_path_prefix(path, sizeof(header.prefix));
+                       int rest = path->len - plen - 1;
+                       if (plen > 0 && rest <= sizeof(header.name)) {
+                               memcpy(header.prefix, path->buf, plen);
+                               memcpy(header.name, path->buf + plen + 1, rest);
+                       } else {
+                               sprintf(header.name, "%s.data",
+                                       sha1_to_hex(sha1));
+                               strbuf_append_ext_header(&ext_header, "path",
+                                                        path->buf, path->len);
+                       }
+               } else
+                       memcpy(header.name, path->buf, path->len);
+       }
+
+       if (S_ISLNK(mode) && buffer) {
+               if (size > sizeof(header.linkname)) {
+                       sprintf(header.linkname, "see %s.paxheader",
+                               sha1_to_hex(sha1));
+                       strbuf_append_ext_header(&ext_header, "linkpath",
+                                                buffer, size);
+               } else
+                       memcpy(header.linkname, buffer, size);
+       }
+
+       sprintf(header.mode, "%07o", mode & 07777);
+       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);
+       strncpy(header.uname, "git", 31);
+       strncpy(header.gname, "git", 31);
+       sprintf(header.devmajor, "%07o", 0);
+       sprintf(header.devminor, "%07o", 0);
+
+       memcpy(header.magic, "ustar", 6);
+       memcpy(header.version, "00", 2);
+
+       sprintf(header.chksum, "%07o", ustar_header_chksum(&header));
+
+       if (ext_header.len > 0) {
+               write_entry(sha1, NULL, 0, ext_header.buf, ext_header.len);
+               free(ext_header.buf);
+       }
+       write_blocked(&header, sizeof(header));
+       if (S_ISREG(mode) && buffer && size > 0)
+               write_blocked(buffer, size);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+       struct strbuf ext_header;
+       ext_header.buf = NULL;
+       ext_header.len = ext_header.alloc = 0;
+       strbuf_append_ext_header(&ext_header, "comment", sha1_to_hex(sha1), 40);
+       write_entry(NULL, NULL, 0, ext_header.buf, ext_header.len);
+       free(ext_header.buf);
+}
+
+static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
+{
+       int pathlen = path->len;
+       struct name_entry entry;
+
+       while (tree_entry(tree, &entry)) {
+               void *eltbuf;
+               char elttype[20];
+               unsigned long eltsize;
+
+               eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
+               if (!eltbuf)
+                       die("cannot read %s", sha1_to_hex(entry.sha1));
+
+               path->len = pathlen;
+               strbuf_append_string(path, entry.path);
+               if (S_ISDIR(entry.mode))
+                       strbuf_append_string(path, "/");
+
+               write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
+
+               if (S_ISDIR(entry.mode)) {
+                       struct tree_desc subtree;
+                       subtree.buf = eltbuf;
+                       subtree.size = eltsize;
+                       traverse_tree(&subtree, path);
+               }
+               free(eltbuf);
+       }
+}
+
+static int generate_tar(int argc, const char **argv, char** envp)
+{
+       unsigned char sha1[20], tree_sha1[20];
+       struct commit *commit;
+       struct tree_desc tree;
+       struct strbuf current_path;
+
+       current_path.buf = xmalloc(PATH_MAX);
+       current_path.alloc = PATH_MAX;
+       current_path.len = current_path.eof = 0;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       switch (argc) {
+       case 3:
+               strbuf_append_string(&current_path, argv[2]);
+               strbuf_append_string(&current_path, "/");
+               /* FALLTHROUGH */
+       case 2:
+               if (get_sha1(argv[1], sha1))
+                       die("Not a valid object name %s", argv[1]);
+               break;
+       default:
+               usage(tar_tree_usage);
+       }
+
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (commit) {
+               write_global_extended_header(commit->object.sha1);
+               archive_time = commit->date;
+       } else
+               archive_time = time(NULL);
+
+       tree.buf = read_object_with_reference(sha1, tree_type, &tree.size,
+                                             tree_sha1);
+       if (!tree.buf)
+               die("not a reference to a tag, commit or tree object: %s",
+                   sha1_to_hex(sha1));
+
+       if (current_path.len > 0)
+               write_entry(tree_sha1, &current_path, 040777, NULL, 0);
+       traverse_tree(&tree, &current_path);
+       write_trailer();
+       free(current_path.buf);
+       return 0;
+}
+
+static const char *exec = "git-upload-tar";
+
+static int remote_tar(int argc, const char **argv)
+{
+       int fd[2], ret, len;
+       pid_t pid;
+       char buf[1024];
+       char *url;
+
+       if (argc < 3 || 4 < argc)
+               usage(tar_tree_usage);
+
+       /* --remote=<repo> */
+       url = strdup(argv[1]+9);
+       pid = git_connect(fd, url, exec);
+       if (pid < 0)
+               return 1;
+
+       packet_write(fd[1], "want %s\n", argv[2]);
+       if (argv[3])
+               packet_write(fd[1], "base %s\n", argv[3]);
+       packet_flush(fd[1]);
+
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (!len)
+               die("git-tar-tree: expected ACK/NAK, got EOF");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (strcmp(buf, "ACK")) {
+               if (5 < len && !strncmp(buf, "NACK ", 5))
+                       die("git-tar-tree: NACK %s", buf + 5);
+               die("git-tar-tree: protocol error");
+       }
+       /* expect a flush */
+       len = packet_read_line(fd[0], buf, sizeof(buf));
+       if (len)
+               die("git-tar-tree: expected a flush");
+
+       /* Now, start reading from fd[0] and spit it out to stdout */
+       ret = copy_fd(fd[0], 1);
+       close(fd[0]);
+
+       ret |= finish_connect(pid);
+       return !!ret;
+}
+
+int cmd_tar_tree(int argc, const char **argv, char **envp)
+{
+       if (argc < 2)
+               usage(tar_tree_usage);
+       if (!strncmp("--remote=", argv[1], 9))
+               return remote_tar(argc, argv);
+       return generate_tar(argc, argv, envp);
+}
diff --git a/builtin-upload-tar.c b/builtin-upload-tar.c
new file mode 100644 (file)
index 0000000..d4fa7b5
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "pkt-line.h"
+#include "exec_cmd.h"
+#include "builtin.h"
+
+static const char upload_tar_usage[] = "git-upload-tar <repo>";
+
+static int nak(const char *reason)
+{
+       packet_write(1, "NACK %s\n", reason);
+       packet_flush(1);
+       return 1;
+}
+
+int cmd_upload_tar(int argc, const char **argv, char **envp)
+{
+       int len;
+       const char *dir = argv[1];
+       char buf[8192];
+       unsigned char sha1[20];
+       char *base = NULL;
+       char hex[41];
+       int ac;
+       const char *av[4];
+
+       if (argc != 2)
+               usage(upload_tar_usage);
+       if (strlen(dir) < sizeof(buf)-1)
+               strcpy(buf, dir); /* enter-repo smudges its argument */
+       else
+               packet_write(1, "NACK insanely long repository name %s\n", dir);
+       if (!enter_repo(buf, 0)) {
+               packet_write(1, "NACK not a git archive %s\n", dir);
+               packet_flush(1);
+               return 1;
+       }
+
+       len = packet_read_line(0, buf, sizeof(buf));
+       if (len < 5 || strncmp("want ", buf, 5))
+               return nak("expected want");
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (get_sha1(buf + 5, sha1))
+               return nak("expected sha1");
+        strcpy(hex, sha1_to_hex(sha1));
+
+       len = packet_read_line(0, buf, sizeof(buf));
+       if (len) {
+               if (len < 5 || strncmp("base ", buf, 5))
+                       return nak("expected (optional) base");
+               if (buf[len-1] == '\n')
+                       buf[--len] = 0;
+               base = strdup(buf + 5);
+               len = packet_read_line(0, buf, sizeof(buf));
+       }
+       if (len)
+               return nak("expected flush");
+
+       packet_write(1, "ACK\n");
+       packet_flush(1);
+
+       ac = 0;
+       av[ac++] = "tar-tree";
+       av[ac++] = hex;
+       if (base)
+               av[ac++] = base;
+       av[ac++] = NULL;
+       execv_git_cmd(av);
+       /* should it return that is an error */
+       return 1;
+}
diff --git a/builtin.h b/builtin.h
new file mode 100644 (file)
index 0000000..ffa9340
--- /dev/null
+++ b/builtin.h
@@ -0,0 +1,48 @@
+#ifndef BUILTIN_H
+#define BUILTIN_H
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+extern const char git_version_string[];
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+#ifdef __GNUC__
+       __attribute__((__format__(__printf__, 3, 4), __noreturn__))
+#endif
+       ;
+
+extern int cmd_help(int argc, const char **argv, char **envp);
+extern int cmd_version(int argc, const char **argv, char **envp);
+
+extern int cmd_whatchanged(int argc, const char **argv, char **envp);
+extern int cmd_show(int argc, const char **argv, char **envp);
+extern int cmd_log(int argc, const char **argv, char **envp);
+extern int cmd_diff(int argc, const char **argv, char **envp);
+extern int cmd_format_patch(int argc, const char **argv, char **envp);
+extern int cmd_count_objects(int argc, const char **argv, char **envp);
+
+extern int cmd_push(int argc, const char **argv, char **envp);
+extern int cmd_grep(int argc, const char **argv, char **envp);
+extern int cmd_rm(int argc, const char **argv, char **envp);
+extern int cmd_add(int argc, const char **argv, char **envp);
+extern int cmd_rev_list(int argc, const char **argv, char **envp);
+extern int cmd_check_ref_format(int argc, const char **argv, char **envp);
+extern int cmd_init_db(int argc, const char **argv, char **envp);
+extern int cmd_tar_tree(int argc, const char **argv, char **envp);
+extern int cmd_upload_tar(int argc, const char **argv, char **envp);
+extern int cmd_ls_files(int argc, const char **argv, char **envp);
+extern int cmd_ls_tree(int argc, const char **argv, char **envp);
+extern int cmd_read_tree(int argc, const char **argv, char **envp);
+extern int cmd_commit_tree(int argc, const char **argv, char **envp);
+extern int cmd_apply(int argc, const char **argv, char **envp);
+extern int cmd_show_branch(int argc, const char **argv, char **envp);
+extern int cmd_diff_files(int argc, const char **argv, char **envp);
+extern int cmd_diff_index(int argc, const char **argv, char **envp);
+extern int cmd_diff_stages(int argc, const char **argv, char **envp);
+extern int cmd_diff_tree(int argc, const char **argv, char **envp);
+extern int cmd_cat_file(int argc, const char **argv, char **envp);
+extern int cmd_rev_parse(int argc, const char **argv, char **envp);
+
+#endif
diff --git a/cache-tree.c b/cache-tree.c
new file mode 100644 (file)
index 0000000..a880c97
--- /dev/null
@@ -0,0 +1,531 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+#define DEBUG 0
+
+struct cache_tree *cache_tree(void)
+{
+       struct cache_tree *it = xcalloc(1, sizeof(struct cache_tree));
+       it->entry_count = -1;
+       return it;
+}
+
+void cache_tree_free(struct cache_tree **it_p)
+{
+       int i;
+       struct cache_tree *it = *it_p;
+
+       if (!it)
+               return;
+       for (i = 0; i < it->subtree_nr; i++)
+               if (it->down[i])
+                       cache_tree_free(&it->down[i]->cache_tree);
+       free(it->down);
+       free(it);
+       *it_p = NULL;
+}
+
+static int subtree_name_cmp(const char *one, int onelen,
+                           const char *two, int twolen)
+{
+       if (onelen < twolen)
+               return -1;
+       if (twolen < onelen)
+               return 1;
+       return memcmp(one, two, onelen);
+}
+
+static int subtree_pos(struct cache_tree *it, const char *path, int pathlen)
+{
+       struct cache_tree_sub **down = it->down;
+       int lo, hi;
+       lo = 0;
+       hi = it->subtree_nr;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct cache_tree_sub *mdl = down[mi];
+               int cmp = subtree_name_cmp(path, pathlen,
+                                          mdl->name, mdl->namelen);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo-1;
+}
+
+static struct cache_tree_sub *find_subtree(struct cache_tree *it,
+                                          const char *path,
+                                          int pathlen,
+                                          int create)
+{
+       struct cache_tree_sub *down;
+       int pos = subtree_pos(it, path, pathlen);
+       if (0 <= pos)
+               return it->down[pos];
+       if (!create)
+               return NULL;
+
+       pos = -pos-1;
+       if (it->subtree_alloc <= it->subtree_nr) {
+               it->subtree_alloc = alloc_nr(it->subtree_alloc);
+               it->down = xrealloc(it->down, it->subtree_alloc *
+                                   sizeof(*it->down));
+       }
+       it->subtree_nr++;
+
+       down = xmalloc(sizeof(*down) + pathlen + 1);
+       down->cache_tree = NULL;
+       down->namelen = pathlen;
+       memcpy(down->name, path, pathlen);
+       down->name[pathlen] = 0;
+
+       if (pos < it->subtree_nr)
+               memmove(it->down + pos + 1,
+                       it->down + pos,
+                       sizeof(down) * (it->subtree_nr - pos - 1));
+       it->down[pos] = down;
+       return down;
+}
+
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path)
+{
+       int pathlen = strlen(path);
+       return find_subtree(it, path, pathlen, 1);
+}
+
+void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
+{
+       /* a/b/c
+        * ==> invalidate self
+        * ==> find "a", have it invalidate "b/c"
+        * a
+        * ==> invalidate self
+        * ==> if "a" exists as a subtree, remove it.
+        */
+       const char *slash;
+       int namelen;
+       struct cache_tree_sub *down;
+
+#if DEBUG
+       fprintf(stderr, "cache-tree invalidate <%s>\n", path);
+#endif
+
+       if (!it)
+               return;
+       slash = strchr(path, '/');
+       it->entry_count = -1;
+       if (!slash) {
+               int pos;
+               namelen = strlen(path);
+               pos = subtree_pos(it, path, namelen);
+               if (0 <= pos) {
+                       cache_tree_free(&it->down[pos]->cache_tree);
+                       free(it->down[pos]);
+                       /* 0 1 2 3 4 5
+                        *       ^     ^subtree_nr = 6
+                        *       pos
+                        * move 4 and 5 up one place (2 entries)
+                        * 2 = 6 - 3 - 1 = subtree_nr - pos - 1
+                        */
+                       memmove(it->down+pos, it->down+pos+1,
+                               sizeof(struct cache_tree_sub *) *
+                               (it->subtree_nr - pos - 1));
+                       it->subtree_nr--;
+               }
+               return;
+       }
+       namelen = slash - path;
+       down = find_subtree(it, path, namelen, 0);
+       if (down)
+               cache_tree_invalidate_path(down->cache_tree, slash + 1);
+}
+
+static int verify_cache(struct cache_entry **cache,
+                       int entries)
+{
+       int i, funny;
+
+       /* Verify that the tree is merged */
+       funny = 0;
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               if (ce_stage(ce)) {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "%s: unmerged (%s)\n",
+                               ce->name, sha1_to_hex(ce->sha1));
+               }
+       }
+       if (funny)
+               return -1;
+
+       /* Also verify that the cache does not have path and path/file
+        * at the same time.  At this point we know the cache has only
+        * stage 0 entries.
+        */
+       funny = 0;
+       for (i = 0; i < entries - 1; i++) {
+               /* path/file always comes after path because of the way
+                * the cache is sorted.  Also path can appear only once,
+                * which means conflicting one would immediately follow.
+                */
+               const char *this_name = cache[i]->name;
+               const char *next_name = cache[i+1]->name;
+               int this_len = strlen(this_name);
+               if (this_len < strlen(next_name) &&
+                   strncmp(this_name, next_name, this_len) == 0 &&
+                   next_name[this_len] == '/') {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "You have both %s and %s\n",
+                               this_name, next_name);
+               }
+       }
+       if (funny)
+               return -1;
+       return 0;
+}
+
+static void discard_unused_subtrees(struct cache_tree *it)
+{
+       struct cache_tree_sub **down = it->down;
+       int nr = it->subtree_nr;
+       int dst, src;
+       for (dst = src = 0; src < nr; src++) {
+               struct cache_tree_sub *s = down[src];
+               if (s->used)
+                       down[dst++] = s;
+               else {
+                       cache_tree_free(&s->cache_tree);
+                       free(s);
+                       it->subtree_nr--;
+               }
+       }
+}
+
+int cache_tree_fully_valid(struct cache_tree *it)
+{
+       int i;
+       if (!it)
+               return 0;
+       if (it->entry_count < 0 || !has_sha1_file(it->sha1))
+               return 0;
+       for (i = 0; i < it->subtree_nr; i++) {
+               if (!cache_tree_fully_valid(it->down[i]->cache_tree))
+                       return 0;
+       }
+       return 1;
+}
+
+static int update_one(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     const char *base,
+                     int baselen,
+                     int missing_ok,
+                     int dryrun)
+{
+       unsigned long size, offset;
+       char *buffer;
+       int i;
+
+       if (0 <= it->entry_count && has_sha1_file(it->sha1))
+               return it->entry_count;
+
+       /*
+        * We first scan for subtrees and update them; we start by
+        * marking existing subtrees -- the ones that are unmarked
+        * should not be in the result.
+        */
+       for (i = 0; i < it->subtree_nr; i++)
+               it->down[i]->used = 0;
+
+       /*
+        * Find the subtrees and update them.
+        */
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, sublen, subcnt;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (!slash)
+                       continue;
+               /*
+                * a/bbb/c (base = a/, slash = /c)
+                * ==>
+                * path+baselen = bbb/c, sublen = 3
+                */
+               sublen = slash - (path + baselen);
+               sub = find_subtree(it, path + baselen, sublen, 1);
+               if (!sub->cache_tree)
+                       sub->cache_tree = cache_tree();
+               subcnt = update_one(sub->cache_tree,
+                                   cache + i, entries - i,
+                                   path,
+                                   baselen + sublen + 1,
+                                   missing_ok,
+                                   dryrun);
+               i += subcnt - 1;
+               sub->used = 1;
+       }
+
+       discard_unused_subtrees(it);
+
+       /*
+        * Then write out the tree object for this level.
+        */
+       size = 8192;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, entlen;
+               const unsigned char *sha1;
+               unsigned mode;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (slash) {
+                       entlen = slash - (path + baselen);
+                       sub = find_subtree(it, path + baselen, entlen, 0);
+                       if (!sub)
+                               die("cache-tree.c: '%.*s' in '%s' not found",
+                                   entlen, path + baselen, path);
+                       i += sub->cache_tree->entry_count - 1;
+                       sha1 = sub->cache_tree->sha1;
+                       mode = S_IFDIR;
+               }
+               else {
+                       sha1 = ce->sha1;
+                       mode = ntohl(ce->ce_mode);
+                       entlen = pathlen - baselen;
+               }
+               if (!missing_ok && !has_sha1_file(sha1))
+                       return error("invalid object %s", sha1_to_hex(sha1));
+
+               if (!ce->ce_mode)
+                       continue; /* entry being removed */
+
+               if (size < offset + entlen + 100) {
+                       size = alloc_nr(offset + entlen + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset,
+                                 "%o %.*s", mode, entlen, path + baselen);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, sha1, 20);
+               offset += 20;
+
+#if DEBUG
+               fprintf(stderr, "cache-tree update-one %o %.*s\n",
+                       mode, entlen, path + baselen);
+#endif
+       }
+
+       if (dryrun) {
+               unsigned char hdr[200];
+               int hdrlen;
+               write_sha1_file_prepare(buffer, offset, tree_type, it->sha1,
+                                       hdr, &hdrlen);
+       }
+       else
+               write_sha1_file(buffer, offset, tree_type, it->sha1);
+       free(buffer);
+       it->entry_count = i;
+#if DEBUG
+       fprintf(stderr, "cache-tree update-one (%d ent, %d subtree) %s\n",
+               it->entry_count, it->subtree_nr,
+               sha1_to_hex(it->sha1));
+#endif
+       return i;
+}
+
+int cache_tree_update(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     int missing_ok,
+                     int dryrun)
+{
+       int i;
+       i = verify_cache(cache, entries);
+       if (i)
+               return i;
+       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       if (i < 0)
+               return i;
+       return 0;
+}
+
+static void *write_one(struct cache_tree *it,
+                      char *path,
+                      int pathlen,
+                      char *buffer,
+                      unsigned long *size,
+                      unsigned long *offset)
+{
+       int i;
+
+       /* One "cache-tree" entry consists of the following:
+        * path (NUL terminated)
+        * entry_count, subtree_nr ("%d %d\n")
+        * tree-sha1 (missing if invalid)
+        * subtree_nr "cache-tree" entries for subtrees.
+        */
+       if (*size < *offset + pathlen + 100) {
+               *size = alloc_nr(*offset + pathlen + 100);
+               buffer = xrealloc(buffer, *size);
+       }
+       *offset += sprintf(buffer + *offset, "%.*s%c%d %d\n",
+                          pathlen, path, 0,
+                          it->entry_count, it->subtree_nr);
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%.*s> (%d ent, %d subtree) %s\n",
+                       pathlen, path, it->entry_count, it->subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%.*s> (%d subtree) invalid\n",
+                       pathlen, path, it->subtree_nr);
+#endif
+
+       if (0 <= it->entry_count) {
+               memcpy(buffer + *offset, it->sha1, 20);
+               *offset += 20;
+       }
+       for (i = 0; i < it->subtree_nr; i++) {
+               struct cache_tree_sub *down = it->down[i];
+               if (i) {
+                       struct cache_tree_sub *prev = it->down[i-1];
+                       if (subtree_name_cmp(down->name, down->namelen,
+                                            prev->name, prev->namelen) <= 0)
+                               die("fatal - unsorted cache subtree");
+               }
+               buffer = write_one(down->cache_tree, down->name, down->namelen,
+                                  buffer, size, offset);
+       }
+       return buffer;
+}
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p)
+{
+       char path[PATH_MAX];
+       unsigned long size = 8192;
+       char *buffer = xmalloc(size);
+
+       *size_p = 0;
+       path[0] = 0;
+       return write_one(root, path, 0, buffer, &size, size_p);
+}
+
+static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
+{
+       const char *buf = *buffer;
+       unsigned long size = *size_p;
+       const char *cp;
+       char *ep;
+       struct cache_tree *it;
+       int i, subtree_nr;
+
+       it = NULL;
+       /* skip name, but make sure name exists */
+       while (size && *buf) {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       it = cache_tree();
+
+       cp = buf;
+       it->entry_count = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       cp = ep;
+       subtree_nr = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       while (size && *buf && *buf != '\n') {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       if (0 <= it->entry_count) {
+               if (size < 20)
+                       goto free_return;
+               memcpy(it->sha1, buf, 20);
+               buf += 20;
+               size -= 20;
+       }
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%s> (%d ent, %d subtree) %s\n",
+                       *buffer, it->entry_count, subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%s> (%d subtrees) invalid\n",
+                       *buffer, subtree_nr);
+#endif
+
+       /*
+        * Just a heuristic -- we do not add directories that often but
+        * we do not want to have to extend it immediately when we do,
+        * hence +2.
+        */
+       it->subtree_alloc = subtree_nr + 2;
+       it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *));
+       for (i = 0; i < subtree_nr; i++) {
+               /* read each subtree */
+               struct cache_tree *sub;
+               struct cache_tree_sub *subtree;
+               const char *name = buf;
+
+               sub = read_one(&buf, &size);
+               if (!sub)
+                       goto free_return;
+               subtree = cache_tree_sub(it, name);
+               subtree->cache_tree = sub;
+       }
+       if (subtree_nr != it->subtree_nr)
+               die("cache-tree: internal error");
+       *buffer = buf;
+       *size_p = size;
+       return it;
+
+ free_return:
+       cache_tree_free(&it);
+       return NULL;
+}
+
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
+{
+       if (buffer[0])
+               return NULL; /* not the whole tree */
+       return read_one(&buffer, &size);
+}
diff --git a/cache-tree.h b/cache-tree.h
new file mode 100644 (file)
index 0000000..72c6480
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef CACHE_TREE_H
+#define CACHE_TREE_H
+
+struct cache_tree;
+struct cache_tree_sub {
+       struct cache_tree *cache_tree;
+       int namelen;
+       int used;
+       char name[FLEX_ARRAY];
+};
+
+struct cache_tree {
+       int entry_count; /* negative means "invalid" */
+       unsigned char sha1[20];
+       int subtree_nr;
+       int subtree_alloc;
+       struct cache_tree_sub **down;
+};
+
+struct cache_tree *cache_tree(void);
+void cache_tree_free(struct cache_tree **);
+void cache_tree_invalidate_path(struct cache_tree *, const char *);
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p);
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
+
+int cache_tree_fully_valid(struct cache_tree *);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+
+#endif
diff --git a/cache.h b/cache.h
new file mode 100644 (file)
index 0000000..d5d7fe4
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,381 @@
+#ifndef CACHE_H
+#define CACHE_H
+
+#include "git-compat-util.h"
+
+#include SHA1_HEADER
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1200
+#define deflateBound(c,s)  ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
+#endif
+
+#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
+#define DTYPE(de)      ((de)->d_type)
+#else
+#undef DT_UNKNOWN
+#undef DT_DIR
+#undef DT_REG
+#undef DT_LNK
+#define DT_UNKNOWN     0
+#define DT_DIR         1
+#define DT_REG         2
+#define DT_LNK         3
+#define DTYPE(de)      DT_UNKNOWN
+#endif
+
+/*
+ * Intensive research over the course of many years has shown that
+ * port 9418 is totally unused by anything else. Or
+ *
+ *     Your search - "port 9418" - did not match any documents.
+ *
+ * as www.google.com puts it.
+ *
+ * This port has been properly assigned for git use by IANA:
+ * git (Assigned-9418) [I06-050728-0001].
+ *
+ *     git  9418/tcp   git pack transfer service
+ *     git  9418/udp   git pack transfer service
+ *
+ * with Linus Torvalds <torvalds@osdl.org> as the point of
+ * contact. September 2005.
+ *
+ * See http://www.iana.org/assignments/port-numbers
+ */
+#define DEFAULT_GIT_PORT 9418
+
+/*
+ * Basic data structures for the directory cache
+ */
+
+#define CACHE_SIGNATURE 0x44495243     /* "DIRC" */
+struct cache_header {
+       unsigned int hdr_signature;
+       unsigned int hdr_version;
+       unsigned int hdr_entries;
+};
+
+/*
+ * The "cache_time" is just the low 32 bits of the
+ * time. It doesn't matter if it overflows - we only
+ * check it for equality in the 32 bits we save.
+ */
+struct cache_time {
+       unsigned int sec;
+       unsigned int nsec;
+};
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * We save the fields in big-endian order to allow using the
+ * index file over NFS transparently.
+ */
+struct cache_entry {
+       struct cache_time ce_ctime;
+       struct cache_time ce_mtime;
+       unsigned int ce_dev;
+       unsigned int ce_ino;
+       unsigned int ce_mode;
+       unsigned int ce_uid;
+       unsigned int ce_gid;
+       unsigned int ce_size;
+       unsigned char sha1[20];
+       unsigned short ce_flags;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+#define CE_NAMEMASK  (0x0fff)
+#define CE_STAGEMASK (0x3000)
+#define CE_UPDATE    (0x4000)
+#define CE_VALID     (0x8000)
+#define CE_STAGESHIFT 12
+
+#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
+#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
+#define ce_size(ce) cache_entry_size(ce_namelen(ce))
+#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
+
+#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
+static inline unsigned int create_ce_mode(unsigned int mode)
+{
+       if (S_ISLNK(mode))
+               return htonl(S_IFLNK);
+       return htonl(S_IFREG | ce_permissions(mode));
+}
+#define canon_mode(mode) \
+       (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
+       S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
+
+#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
+
+extern struct cache_entry **active_cache;
+extern unsigned int active_nr, active_alloc, active_cache_changed;
+extern struct cache_tree *active_cache_tree;
+
+#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
+#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
+#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
+#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+
+extern char *get_git_dir(void);
+extern char *get_object_directory(void);
+extern char *get_refs_directory(void);
+extern char *get_index_file(void);
+extern char *get_graft_file(void);
+
+#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
+
+extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern const char *setup_git_directory_gently(int *);
+extern const char *setup_git_directory(void);
+extern const char *prefix_path(const char *prefix, int len, const char *path);
+extern const char *prefix_filename(const char *prefix, int len, const char *path);
+extern void verify_filename(const char *prefix, const char *name);
+extern void verify_non_filename(const char *prefix, const char *name);
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/* Initialize and use the cache information */
+extern int read_cache(void);
+extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+extern int verify_path(const char *path);
+extern int cache_name_pos(const char *name, int namelen);
+#define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
+#define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
+#define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
+extern int add_cache_entry(struct cache_entry *ce, int option);
+extern int remove_cache_entry_at(int pos);
+extern int remove_file_from_cache(const char *path);
+extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
+extern int ce_match_stat(struct cache_entry *ce, struct stat *st, int);
+extern int ce_modified(struct cache_entry *ce, struct stat *st, int);
+extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
+extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
+extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
+extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
+extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
+
+#define REFRESH_REALLY         0x0001  /* ignore_valid */
+#define REFRESH_UNMERGED       0x0002  /* allow unmerged */
+#define REFRESH_QUIET          0x0004  /* be quiet about it */
+#define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
+extern int refresh_cache(unsigned int flags);
+
+struct lock_file {
+       struct lock_file *next;
+       char filename[PATH_MAX];
+};
+extern int hold_lock_file_for_update(struct lock_file *, const char *path);
+extern int commit_lock_file(struct lock_file *);
+extern void rollback_lock_file(struct lock_file *);
+
+/* Environment bits from configuration mechanism */
+extern int trust_executable_bit;
+extern int assume_unchanged;
+extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
+extern int warn_ambiguous_refs;
+extern int diff_rename_limit_default;
+extern int shared_repository;
+extern const char *apply_default_whitespace;
+
+#define GIT_REPO_VERSION 0
+extern int repository_format_version;
+extern int check_repository_format(void);
+
+#define MTIME_CHANGED  0x0001
+#define CTIME_CHANGED  0x0002
+#define OWNER_CHANGED  0x0004
+#define MODE_CHANGED    0x0008
+#define INODE_CHANGED   0x0010
+#define DATA_CHANGED    0x0020
+#define TYPE_CHANGED    0x0040
+
+/* Return a statically allocated filename matching the sha1 signature */
+extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *sha1_file_name(const unsigned char *sha1);
+extern char *sha1_pack_name(const unsigned char *sha1);
+extern char *sha1_pack_index_name(const unsigned char *sha1);
+extern const char *find_unique_abbrev(const unsigned char *sha1, int);
+extern const unsigned char null_sha1[20];
+
+int git_mkstemp(char *path, size_t n, const char *template);
+
+int adjust_shared_perm(const char *path);
+int safe_create_leading_directories(char *path);
+char *safe_strncpy(char *, const char *, size_t);
+char *enter_repo(char *path, int strict);
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
+extern int parse_sha1_header(char *hdr, char *type, unsigned long *sizep);
+extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
+extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
+extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
+extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern char *write_sha1_file_prepare(void *buf,
+                                    unsigned long len,
+                                    const char *type,
+                                    unsigned char *sha1,
+                                    unsigned char *hdr,
+                                    int *hdrlen);
+
+extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
+
+extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
+                             size_t bufsize, size_t *bufposn);
+extern int write_sha1_to_fd(int fd, const unsigned char *sha1);
+extern int move_temp_to_file(const char *tmpfile, char *filename);
+
+extern int has_sha1_pack(const unsigned char *sha1);
+extern int has_sha1_file(const unsigned char *sha1);
+
+extern int has_pack_file(const unsigned char *sha1);
+extern int has_pack_index(const unsigned char *sha1);
+
+/* Convert to/from hex/sha1 representation */
+#define MINIMUM_ABBREV 4
+#define DEFAULT_ABBREV 7
+
+extern int get_sha1(const char *str, unsigned char *sha1);
+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);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
+
+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);
+
+extern void *read_object_with_reference(const unsigned char *sha1,
+                                       const char *required_type,
+                                       unsigned long *size,
+                                       unsigned char *sha1_ret);
+
+const char *show_date(unsigned long time, int timezone);
+const char *show_rfc2822_date(unsigned long time, int timezone);
+int parse_date(const char *date, char *buf, int bufsize);
+void datestamp(char *buf, int bufsize);
+unsigned long approxidate(const char *);
+
+extern int setup_ident(void);
+extern const char *git_author_info(int);
+extern const char *git_committer_info(int);
+
+struct checkout {
+       const char *base_dir;
+       int base_dir_len;
+       unsigned force:1,
+                quiet:1,
+                not_new:1,
+                refresh_cache:1;
+};
+
+extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);
+
+extern struct alternate_object_database {
+       struct alternate_object_database *next;
+       char *name;
+       char base[FLEX_ARRAY]; /* more */
+} *alt_odb_list;
+extern void prepare_alt_odb(void);
+
+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;
+       int pack_local;
+       unsigned char sha1[20];
+       /* something like ".git/objects/pack/xxxxx.pack" */
+       char pack_name[FLEX_ARRAY]; /* more */
+} *packed_git;
+
+struct pack_entry {
+       unsigned int offset;
+       unsigned char sha1[20];
+       struct packed_git *p;
+};
+
+struct ref {
+       struct ref *next;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       unsigned char force;
+       struct ref *peer_ref; /* when renaming */
+       char name[FLEX_ARRAY]; /* more */
+};
+
+extern int git_connect(int fd[2], char *url, const char *prog);
+extern int finish_connect(pid_t pid);
+extern int path_match(const char *path, int nr, char **match);
+extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+                     int nr_refspec, char **refspec, int all);
+extern int get_ack(int fd, unsigned char *result_sha1);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern int server_supports(const char *feature);
+
+extern struct packed_git *parse_pack_index(unsigned char *sha1);
+extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
+                                               char *idx_path);
+
+extern void prepare_packed_git(void);
+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 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 int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
+extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
+
+/* Dumb servers support */
+extern int update_server_info(int);
+
+typedef int (*config_fn_t)(const char *, const char *);
+extern int git_default_config(const char *, const char *);
+extern int git_config_from_file(config_fn_t fn, const char *);
+extern int git_config(config_fn_t fn);
+extern int git_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 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 int copy_fd(int ifd, int ofd);
+
+/* Finish off pack transfer receiving end */
+extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
+extern int receive_keep_pack(int fd[2], const char *me, int quiet);
+
+/* pager.c */
+extern void setup_pager(void);
+
+/* base85 */
+int decode_85(char *dst, char *line, int linelen);
+void encode_85(char *buf, unsigned char *data, int bytes);
+
+#endif /* CACHE_H */
diff --git a/checkout-index.c b/checkout-index.c
new file mode 100644 (file)
index 0000000..ea40bc2
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Check-out files from the "current cache directory"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Careful: order of argument flags does matter. For example,
+ *
+ *     git-checkout-index -a -f file.c
+ *
+ * Will first check out all files listed in the cache (but not
+ * overwrite any old ones), and then force-checkout "file.c" a
+ * second time (ie that one _will_ overwrite any old contents
+ * with the same filename).
+ *
+ * Also, just doing "git-checkout-index" does nothing. You probably
+ * meant "git-checkout-index -a". And if you want to force it, you
+ * want "git-checkout-index -f -a".
+ *
+ * Intuitiveness is not the goal here. Repeatability is. The
+ * reason for the "no arguments means no work" thing is that
+ * from scripts you are supposed to be able to do things like
+ *
+ *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
+ *
+ * or:
+ *
+ *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
+ * which will force all existing *.h files to be replaced with
+ * their cached copies. If an empty command line implied "all",
+ * then this would force-refresh everything in the cache, which
+ * was not the point.
+ *
+ * Oh, and the "--" is just a good idea when you know the rest
+ * will be filenames. Just so that you wouldn't have a filename
+ * of "-a" causing problems (not possible in the above example,
+ * but get used to it in scripting!).
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+#include "cache-tree.h"
+
+#define CHECKOUT_ALL 4
+static const char *prefix;
+static int prefix_length;
+static int line_termination = '\n';
+static int checkout_stage; /* default to checkout stage0 */
+static int to_tempfile;
+static char topath[4][MAXPATHLEN+1];
+
+static struct checkout state = {
+       .base_dir = "",
+       .base_dir_len = 0,
+       .force = 0,
+       .quiet = 0,
+       .not_new = 0,
+       .refresh_cache = 0,
+};
+
+static void write_tempfile_record (const char *name)
+{
+       int i;
+
+       if (CHECKOUT_ALL == checkout_stage) {
+               for (i = 1; i < 4; i++) {
+                       if (i > 1)
+                               putchar(' ');
+                       if (topath[i][0])
+                               fputs(topath[i], stdout);
+                       else
+                               putchar('.');
+               }
+       } else
+               fputs(topath[checkout_stage], stdout);
+
+       putchar('\t');
+       write_name_quoted("", 0, name + prefix_length,
+               line_termination, stdout);
+       putchar(line_termination);
+
+       for (i = 0; i < 4; i++) {
+               topath[i][0] = 0;
+       }
+}
+
+static int checkout_file(const char *name)
+{
+       int namelen = strlen(name);
+       int pos = cache_name_pos(name, namelen);
+       int has_same_name = 0;
+       int did_checkout = 0;
+       int errs = 0;
+
+       if (pos < 0)
+               pos = -pos - 1;
+
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos];
+               if (ce_namelen(ce) != namelen ||
+                   memcmp(ce->name, name, namelen))
+                       break;
+               has_same_name = 1;
+               pos++;
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+                       continue;
+               did_checkout = 1;
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+                       errs++;
+       }
+
+       if (did_checkout) {
+               if (to_tempfile)
+                       write_tempfile_record(name);
+               return errs > 0 ? -1 : 0;
+       }
+
+       if (!state.quiet) {
+               fprintf(stderr, "git-checkout-index: %s ", name);
+               if (!has_same_name)
+                       fprintf(stderr, "is not in the cache");
+               else if (checkout_stage)
+                       fprintf(stderr, "does not exist at stage %d",
+                               checkout_stage);
+               else
+                       fprintf(stderr, "is unmerged");
+               fputc('\n', stderr);
+       }
+       return -1;
+}
+
+static int checkout_all(void)
+{
+       int i, errs = 0;
+       struct cache_entry* last_ce = NULL;
+
+       for (i = 0; i < active_nr ; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+                       continue;
+               if (prefix && *prefix &&
+                   (ce_namelen(ce) <= prefix_length ||
+                    memcmp(prefix, ce->name, prefix_length)))
+                       continue;
+               if (last_ce && to_tempfile) {
+                       if (ce_namelen(last_ce) != ce_namelen(ce)
+                           || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
+                               write_tempfile_record(last_ce->name);
+               }
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+                       errs++;
+               last_ce = ce;
+       }
+       if (last_ce && to_tempfile)
+               write_tempfile_record(last_ce->name);
+       if (errs)
+               /* we have already done our error reporting.
+                * exit with the same code as die().
+                */
+               exit(128);
+       return 0;
+}
+
+static const char checkout_cache_usage[] =
+"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
+
+static struct lock_file lock_file;
+
+int main(int argc, char **argv)
+{
+       int i;
+       int newfd = -1;
+       int all = 0;
+       int read_from_stdin = 0;
+
+       prefix = setup_git_directory();
+       git_config(git_default_config);
+       prefix_length = prefix ? strlen(prefix) : 0;
+
+       if (read_cache() < 0) {
+               die("invalid cache");
+       }
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) {
+                       state.force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+                       state.quiet = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) {
+                       state.not_new = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
+                       state.refresh_cache = 1;
+                       if (newfd < 0)
+                               newfd = hold_lock_file_for_update
+                                       (&lock_file, get_index_file());
+                       if (newfd < 0)
+                               die("cannot open index.lock file.");
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       if (i != argc - 1)
+                               die("--stdin must be at the end");
+                       read_from_stdin = 1;
+                       i++; /* do not consider arg as a file name */
+                       break;
+               }
+               if (!strcmp(arg, "--temp")) {
+                       to_tempfile = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       state.base_dir = arg+9;
+                       state.base_dir_len = strlen(state.base_dir);
+                       continue;
+               }
+               if (!strncmp(arg, "--stage=", 8)) {
+                       if (!strcmp(arg + 8, "all")) {
+                               to_tempfile = 1;
+                               checkout_stage = CHECKOUT_ALL;
+                       } else {
+                               int ch = arg[8];
+                               if ('1' <= ch && ch <= '3')
+                                       checkout_stage = arg[8] - '0';
+                               else
+                                       die("stage should be between 1 and 3 or all");
+                       }
+                       continue;
+               }
+               if (arg[0] == '-')
+                       usage(checkout_cache_usage);
+               break;
+       }
+
+       if (state.base_dir_len || to_tempfile) {
+               /* when --prefix is specified we do not
+                * want to update cache.
+                */
+               if (state.refresh_cache) {
+                       close(newfd); newfd = -1;
+                       rollback_lock_file(&lock_file);
+               }
+               state.refresh_cache = 0;
+       }
+
+       /* Check out named files first */
+       for ( ; i < argc; i++) {
+               const char *arg = argv[i];
+               const char *p;
+
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and explicit filenames");
+               if (read_from_stdin)
+                       die("git-checkout-index: don't mix '--stdin' and explicit filenames");
+               p = prefix_path(prefix, prefix_length, arg);
+               checkout_file(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
+       }
+
+       if (read_from_stdin) {
+               struct strbuf buf;
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and '--stdin'");
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       const char *p;
+
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       checkout_file(p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char *)p);
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
+       if (all)
+               checkout_all();
+
+       if (0 <= newfd &&
+           (write_cache(newfd, active_cache, active_nr) ||
+            commit_lock_file(&lock_file)))
+               die("Unable to write new index file");
+       return 0;
+}
diff --git a/clone-pack.c b/clone-pack.c
new file mode 100644 (file)
index 0000000..a4370f5
--- /dev/null
@@ -0,0 +1,186 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char clone_pack_usage[] =
+"git-clone-pack [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
+static const char *exec = "git-upload-pack";
+
+static int quiet = 0;
+
+static void clone_handshake(int fd[2], struct ref *ref)
+{
+       unsigned char sha1[20];
+
+       while (ref) {
+               packet_write(fd[1], "want %s\n", sha1_to_hex(ref->old_sha1));
+               ref = ref->next;
+       }
+       packet_flush(fd[1]);
+
+       /* We don't have nuttin' */
+       packet_write(fd[1], "done\n");
+       if (get_ack(fd[0], sha1))
+               error("Huh! git-clone-pack got positive ack for %s", sha1_to_hex(sha1));
+}
+
+static int is_master(struct ref *ref)
+{
+       return !strcmp(ref->name, "refs/heads/master");
+}
+
+static void write_one_ref(struct ref *ref)
+{
+       char *path = git_path("%s", ref->name);
+       int fd;
+       char *hex;
+
+       if (!strncmp(ref->name, "refs/", 5) &&
+           check_ref_format(ref->name + 5)) {
+               error("refusing to create funny ref '%s' locally", ref->name);
+               return;
+       }
+
+       if (safe_create_leading_directories(path))
+               die("unable to create leading directory for %s", ref->name);
+       fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0)
+               die("unable to create ref %s", ref->name);
+       hex = sha1_to_hex(ref->old_sha1);
+       hex[40] = '\n';
+       if (write(fd, hex, 41) != 41)
+               die("unable to write ref %s", ref->name);
+       close(fd);
+}
+
+static void write_refs(struct ref *ref)
+{
+       struct ref *head = NULL, *head_ptr, *master_ref;
+       char *head_path;
+
+       /* Upload-pack must report HEAD first */
+       if (!strcmp(ref->name, "HEAD")) {
+               head = ref;
+               ref = ref->next;
+       }
+       head_ptr = NULL;
+       master_ref = NULL;
+       while (ref) {
+               if (is_master(ref))
+                       master_ref = ref;
+               if (head &&
+                   !memcmp(ref->old_sha1, head->old_sha1, 20) &&
+                   !strncmp(ref->name, "refs/heads/",11) &&
+                   (!head_ptr || ref == master_ref))
+                       head_ptr = ref;
+
+               write_one_ref(ref);
+               ref = ref->next;
+       }
+       if (!head) {
+               fprintf(stderr, "No HEAD in remote.\n");
+               return;
+       }
+
+       head_path = strdup(git_path("HEAD"));
+       if (!head_ptr) {
+               /*
+                * If we had a master ref, and it wasn't HEAD, we need to undo the
+                * symlink, and write a standalone HEAD. Give a warning, because that's
+                * really really wrong.
+                */
+               if (master_ref) {
+                       error("HEAD doesn't point to any refs! Making standalone HEAD");
+                       unlink(head_path);
+               }
+               write_one_ref(head);
+               free(head_path);
+               return;
+       }
+
+       /* We reset to the master branch if it's available */
+       if (master_ref)
+               return;
+
+       fprintf(stderr, "Setting HEAD to %s\n", head_ptr->name);
+
+       /*
+        * Uhhuh. Other end didn't have master. We start HEAD off with
+        * the first branch with the same value.
+        */
+       if (create_symref(head_path, head_ptr->name) < 0)
+               die("unable to link HEAD to %s", head_ptr->name);
+       free(head_path);
+}
+
+static int clone_pack(int fd[2], int nr_match, char **match)
+{
+       struct ref *refs;
+       int status;
+
+       get_remote_heads(fd[0], &refs, nr_match, match, 1);
+       if (!refs) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
+       }
+       clone_handshake(fd, refs);
+
+       status = receive_keep_pack(fd, "git-clone-pack", quiet);
+       if (!quiet)
+               fprintf(stderr, "\n");
+
+       if (!status) {
+               if (nr_match == 0)
+                       write_refs(refs);
+               else
+                       while (refs) {
+                               printf("%s %s\n",
+                                      sha1_to_hex(refs->old_sha1),
+                                      refs->name);
+                               refs = refs->next;
+                       }
+       }
+       return status;
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret, nr_heads;
+       char *dest = NULL, **heads;
+       int fd[2];
+       pid_t pid;
+
+       setup_git_directory();
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp("-q", arg)) {
+                               quiet = 1;
+                               continue;
+                       }
+                       if (!strncmp("--exec=", arg, 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       usage(clone_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(clone_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = clone_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/combine-diff.c b/combine-diff.c
new file mode 100644 (file)
index 0000000..64b20cc
--- /dev/null
@@ -0,0 +1,923 @@
+#include "cache.h"
+#include "commit.h"
+#include "blob.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "quote.h"
+#include "xdiff-interface.h"
+#include "log-tree.h"
+
+static int uninteresting(struct diff_filepair *p)
+{
+       if (diff_unmodified_pair(p))
+               return 1;
+       return 0;
+}
+
+static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct combine_diff_path *p;
+       int i;
+
+       if (!n) {
+               struct combine_diff_path *list = NULL, **tail = &list;
+               for (i = 0; i < q->nr; i++) {
+                       int len;
+                       const char *path;
+                       if (uninteresting(q->queue[i]))
+                               continue;
+                       path = q->queue[i]->two->path;
+                       len = strlen(path);
+                       p = xmalloc(combine_diff_path_size(num_parent, len));
+                       p->path = (char*) &(p->parent[num_parent]);
+                       memcpy(p->path, path, len);
+                       p->path[len] = 0;
+                       p->len = len;
+                       p->next = NULL;
+                       memset(p->parent, 0,
+                              sizeof(p->parent[0]) * num_parent);
+
+                       memcpy(p->sha1, q->queue[i]->two->sha1, 20);
+                       p->mode = q->queue[i]->two->mode;
+                       memcpy(p->parent[n].sha1, q->queue[i]->one->sha1, 20);
+                       p->parent[n].mode = q->queue[i]->one->mode;
+                       p->parent[n].status = q->queue[i]->status;
+                       *tail = p;
+                       tail = &p->next;
+               }
+               return list;
+       }
+
+       for (p = curr; p; p = p->next) {
+               int found = 0;
+               if (!p->len)
+                       continue;
+               for (i = 0; i < q->nr; i++) {
+                       const char *path;
+                       int len;
+
+                       if (uninteresting(q->queue[i]))
+                               continue;
+                       path = q->queue[i]->two->path;
+                       len = strlen(path);
+                       if (len == p->len && !memcmp(path, p->path, len)) {
+                               found = 1;
+                               memcpy(p->parent[n].sha1,
+                                      q->queue[i]->one->sha1, 20);
+                               p->parent[n].mode = q->queue[i]->one->mode;
+                               p->parent[n].status = q->queue[i]->status;
+                               break;
+                       }
+               }
+               if (!found)
+                       p->len = 0;
+       }
+       return curr;
+}
+
+/* Lines lost from parent */
+struct lline {
+       struct lline *next;
+       int len;
+       unsigned long parent_map;
+       char line[FLEX_ARRAY];
+};
+
+/* Lines surviving in the merge result */
+struct sline {
+       struct lline *lost_head, **lost_tail;
+       char *bol;
+       int len;
+       /* bit 0 up to (N-1) are on if the parent has this line (i.e.
+        * we did not change it).
+        * bit N is used for "interesting" lines, including context.
+        */
+       unsigned long flag;
+       unsigned long *p_lno;
+};
+
+static char *grab_blob(const unsigned char *sha1, unsigned long *size)
+{
+       char *blob;
+       char type[20];
+       if (!memcmp(sha1, null_sha1, 20)) {
+               /* deleted blob */
+               *size = 0;
+               return xcalloc(1, 1);
+       }
+       blob = read_sha1_file(sha1, type, size);
+       if (strcmp(type, blob_type))
+               die("object '%s' is not a blob!", sha1_to_hex(sha1));
+       return blob;
+}
+
+static void append_lost(struct sline *sline, int n, const char *line, int len)
+{
+       struct lline *lline;
+       unsigned long this_mask = (1UL<<n);
+       if (line[len-1] == '\n')
+               len--;
+
+       /* Check to see if we can squash things */
+       if (sline->lost_head) {
+               struct lline *last_one = NULL;
+               /* We cannot squash it with earlier one */
+               for (lline = sline->lost_head;
+                    lline;
+                    lline = lline->next)
+                       if (lline->parent_map & this_mask)
+                               last_one = lline;
+               lline = last_one ? last_one->next : sline->lost_head;
+               while (lline) {
+                       if (lline->len == len &&
+                           !memcmp(lline->line, line, len)) {
+                               lline->parent_map |= this_mask;
+                               return;
+                       }
+                       lline = lline->next;
+               }
+       }
+
+       lline = xmalloc(sizeof(*lline) + len + 1);
+       lline->len = len;
+       lline->next = NULL;
+       lline->parent_map = this_mask;
+       memcpy(lline->line, line, len);
+       lline->line[len] = 0;
+       *sline->lost_tail = lline;
+       sline->lost_tail = &lline->next;
+}
+
+struct combine_diff_state {
+       struct xdiff_emit_state xm;
+
+       unsigned int lno;
+       int ob, on, nb, nn;
+       unsigned long nmask;
+       int num_parent;
+       int n;
+       struct sline *sline;
+       struct sline *lost_bucket;
+};
+
+static void consume_line(void *state_, char *line, unsigned long len)
+{
+       struct combine_diff_state *state = state_;
+       if (5 < len && !memcmp("@@ -", line, 4)) {
+               if (parse_hunk_header(line, len,
+                                     &state->ob, &state->on,
+                                     &state->nb, &state->nn))
+                       return;
+               state->lno = state->nb;
+               if (!state->nb)
+                       /* @@ -1,2 +0,0 @@ to remove the
+                        * first two lines...
+                        */
+                       state->nb = 1;
+               if (state->nn == 0)
+                       /* @@ -X,Y +N,0 @@ removed Y lines
+                        * that would have come *after* line N
+                        * in the result.  Our lost buckets hang
+                        * to the line after the removed lines,
+                        */
+                       state->lost_bucket = &state->sline[state->nb];
+               else
+                       state->lost_bucket = &state->sline[state->nb-1];
+               if (!state->sline[state->nb-1].p_lno)
+                       state->sline[state->nb-1].p_lno =
+                               xcalloc(state->num_parent,
+                                       sizeof(unsigned long));
+               state->sline[state->nb-1].p_lno[state->n] = state->ob;
+               return;
+       }
+       if (!state->lost_bucket)
+               return; /* not in any hunk yet */
+       switch (line[0]) {
+       case '-':
+               append_lost(state->lost_bucket, state->n, line+1, len-1);
+               break;
+       case '+':
+               state->sline[state->lno-1].flag |= state->nmask;
+               state->lno++;
+               break;
+       }
+}
+
+static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
+                        struct sline *sline, int cnt, int n, int num_parent)
+{
+       unsigned int p_lno, lno;
+       unsigned long nmask = (1UL << n);
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       mmfile_t parent_file;
+       xdemitcb_t ecb;
+       struct combine_diff_state state;
+       unsigned long sz;
+
+       if (!cnt)
+               return; /* result deleted */
+
+       parent_file.ptr = grab_blob(parent, &sz);
+       parent_file.size = sz;
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 0;
+       xecfg.flags = 0;
+       ecb.outf = xdiff_outf;
+       ecb.priv = &state;
+       memset(&state, 0, sizeof(state));
+       state.xm.consume = consume_line;
+       state.nmask = nmask;
+       state.sline = sline;
+       state.lno = 1;
+       state.num_parent = num_parent;
+       state.n = n;
+
+       xdl_diff(&parent_file, result_file, &xpp, &xecfg, &ecb);
+       free(parent_file.ptr);
+
+       /* Assign line numbers for this parent.
+        *
+        * sline[lno].p_lno[n] records the first line number
+        * (counting from 1) for parent N if the final hunk display
+        * started by showing sline[lno] (possibly showing the lost
+        * lines attached to it first).
+        */
+       for (lno = 0,  p_lno = 1; lno <= cnt; lno++) {
+               struct lline *ll;
+               sline[lno].p_lno[n] = p_lno;
+
+               /* How many lines would this sline advance the p_lno? */
+               ll = sline[lno].lost_head;
+               while (ll) {
+                       if (ll->parent_map & nmask)
+                               p_lno++; /* '-' means parent had it */
+                       ll = ll->next;
+               }
+               if (lno < cnt && !(sline[lno].flag & nmask))
+                       p_lno++; /* no '+' means parent had it */
+       }
+       sline[lno].p_lno[n] = p_lno; /* trailer */
+}
+
+static unsigned long context = 3;
+static char combine_marker = '@';
+
+static int interesting(struct sline *sline, unsigned long all_mask)
+{
+       /* If some parents lost lines here, or if we have added to
+        * some parent, it is interesting.
+        */
+       return ((sline->flag & all_mask) || sline->lost_head);
+}
+
+static unsigned long adjust_hunk_tail(struct sline *sline,
+                                     unsigned long all_mask,
+                                     unsigned long hunk_begin,
+                                     unsigned long i)
+{
+       /* i points at the first uninteresting line.  If the last line
+        * of the hunk was interesting only because it has some
+        * deletion, then it is not all that interesting for the
+        * purpose of giving trailing context lines.  This is because
+        * we output '-' line and then unmodified sline[i-1] itself in
+        * that case which gives us one extra context line.
+        */
+       if ((hunk_begin + 1 <= i) && !(sline[i-1].flag & all_mask))
+               i--;
+       return i;
+}
+
+static unsigned long find_next(struct sline *sline,
+                              unsigned long mark,
+                              unsigned long i,
+                              unsigned long cnt,
+                              int uninteresting)
+{
+       /* We have examined up to i-1 and are about to look at i.
+        * Find next interesting or uninteresting line.  Here,
+        * "interesting" does not mean interesting(), but marked by
+        * the give_context() function below (i.e. it includes context
+        * lines that are not interesting to interesting() function
+        * that are surrounded by interesting() ones.
+        */
+       while (i <= cnt)
+               if (uninteresting
+                   ? !(sline[i].flag & mark)
+                   : (sline[i].flag & mark))
+                       return i;
+               else
+                       i++;
+       return i;
+}
+
+static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
+{
+       unsigned long all_mask = (1UL<<num_parent) - 1;
+       unsigned long mark = (1UL<<num_parent);
+       unsigned long i;
+
+       /* Two groups of interesting lines may have a short gap of
+        * unintersting lines.  Connect such groups to give them a
+        * bit of context.
+        *
+        * We first start from what the interesting() function says,
+        * and mark them with "mark", and paint context lines with the
+        * mark.  So interesting() would still say false for such context
+        * lines but they are treated as "interesting" in the end.
+        */
+       i = find_next(sline, mark, 0, cnt, 0);
+       if (cnt < i)
+               return 0;
+
+       while (i <= cnt) {
+               unsigned long j = (context < i) ? (i - context) : 0;
+               unsigned long k;
+
+               /* Paint a few lines before the first interesting line. */
+               while (j < i)
+                       sline[j++].flag |= mark;
+
+       again:
+               /* we know up to i is to be included.  where does the
+                * next uninteresting one start?
+                */
+               j = find_next(sline, mark, i, cnt, 1);
+               if (cnt < j)
+                       break; /* the rest are all interesting */
+
+               /* lookahead context lines */
+               k = find_next(sline, mark, j, cnt, 0);
+               j = adjust_hunk_tail(sline, all_mask, i, j);
+
+               if (k < j + context) {
+                       /* k is interesting and [j,k) are not, but
+                        * paint them interesting because the gap is small.
+                        */
+                       while (j < k)
+                               sline[j++].flag |= mark;
+                       i = k;
+                       goto again;
+               }
+
+               /* j is the first uninteresting line and there is
+                * no overlap beyond it within context lines.  Paint
+                * the trailing edge a bit.
+                */
+               i = k;
+               k = (j + context < cnt+1) ? j + context : cnt+1;
+               while (j < k)
+                       sline[j++].flag |= mark;
+       }
+       return 1;
+}
+
+static int make_hunks(struct sline *sline, unsigned long cnt,
+                      int num_parent, int dense)
+{
+       unsigned long all_mask = (1UL<<num_parent) - 1;
+       unsigned long mark = (1UL<<num_parent);
+       unsigned long i;
+       int has_interesting = 0;
+
+       for (i = 0; i <= cnt; i++) {
+               if (interesting(&sline[i], all_mask))
+                       sline[i].flag |= mark;
+               else
+                       sline[i].flag &= ~mark;
+       }
+       if (!dense)
+               return give_context(sline, cnt, num_parent);
+
+       /* Look at each hunk, and if we have changes from only one
+        * parent, or the changes are the same from all but one
+        * parent, mark that uninteresting.
+        */
+       i = 0;
+       while (i <= cnt) {
+               unsigned long j, hunk_begin, hunk_end;
+               unsigned long same_diff;
+               while (i <= cnt && !(sline[i].flag & mark))
+                       i++;
+               if (cnt < i)
+                       break; /* No more interesting hunks */
+               hunk_begin = i;
+               for (j = i + 1; j <= cnt; j++) {
+                       if (!(sline[j].flag & mark)) {
+                               /* Look beyond the end to see if there
+                                * is an interesting line after this
+                                * hunk within context span.
+                                */
+                               unsigned long la; /* lookahead */
+                               int contin = 0;
+                               la = adjust_hunk_tail(sline, all_mask,
+                                                    hunk_begin, j);
+                               la = (la + context < cnt + 1) ?
+                                       (la + context) : cnt + 1;
+                               while (j <= --la) {
+                                       if (sline[la].flag & mark) {
+                                               contin = 1;
+                                               break;
+                                       }
+                               }
+                               if (!contin)
+                                       break;
+                               j = la;
+                       }
+               }
+               hunk_end = j;
+
+               /* [i..hunk_end) are interesting.  Now is it really
+                * interesting?  We check if there are only two versions
+                * and the result matches one of them.  That is, we look
+                * at:
+                *   (+) line, which records lines added to which parents;
+                *       this line appears in the result.
+                *   (-) line, which records from what parents the line
+                *       was removed; this line does not appear in the result.
+                * then check the set of parents the result has difference
+                * from, from all lines.  If there are lines that has
+                * different set of parents that the result has differences
+                * from, that means we have more than two versions.
+                *
+                * Even when we have only two versions, if the result does
+                * not match any of the parents, the it should be considered
+                * interesting.  In such a case, we would have all '+' line.
+                * After passing the above "two versions" test, that would
+                * appear as "the same set of parents" to be "all parents".
+                */
+               same_diff = 0;
+               has_interesting = 0;
+               for (j = i; j < hunk_end && !has_interesting; j++) {
+                       unsigned long this_diff = sline[j].flag & all_mask;
+                       struct lline *ll = sline[j].lost_head;
+                       if (this_diff) {
+                               /* This has some changes.  Is it the
+                                * same as others?
+                                */
+                               if (!same_diff)
+                                       same_diff = this_diff;
+                               else if (same_diff != this_diff) {
+                                       has_interesting = 1;
+                                       break;
+                               }
+                       }
+                       while (ll && !has_interesting) {
+                               /* Lost this line from these parents;
+                                * who are they?  Are they the same?
+                                */
+                               this_diff = ll->parent_map;
+                               if (!same_diff)
+                                       same_diff = this_diff;
+                               else if (same_diff != this_diff) {
+                                       has_interesting = 1;
+                               }
+                               ll = ll->next;
+                       }
+               }
+
+               if (!has_interesting && same_diff != all_mask) {
+                       /* This hunk is not that interesting after all */
+                       for (j = hunk_begin; j < hunk_end; j++)
+                               sline[j].flag &= ~mark;
+               }
+               i = hunk_end;
+       }
+
+       has_interesting = give_context(sline, cnt, num_parent);
+       return has_interesting;
+}
+
+static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, unsigned long cnt, int n)
+{
+       l0 = sline[l0].p_lno[n];
+       l1 = sline[l1].p_lno[n];
+       printf(" -%lu,%lu", l0, l1-l0);
+}
+
+static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent)
+{
+       unsigned long mark = (1UL<<num_parent);
+       int i;
+       unsigned long lno = 0;
+
+       if (!cnt)
+               return; /* result deleted */
+
+       while (1) {
+               struct sline *sl = &sline[lno];
+               unsigned long hunk_end;
+               unsigned long rlines;
+               while (lno <= cnt && !(sline[lno].flag & mark))
+                       lno++;
+               if (cnt < lno)
+                       break;
+               else {
+                       for (hunk_end = lno + 1; hunk_end <= cnt; hunk_end++)
+                               if (!(sline[hunk_end].flag & mark))
+                                       break;
+               }
+               rlines = hunk_end - lno;
+               if (cnt < hunk_end)
+                       rlines--; /* pointing at the last delete hunk */
+               for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+               for (i = 0; i < num_parent; i++)
+                       show_parent_lno(sline, lno, hunk_end, cnt, i);
+               printf(" +%lu,%lu ", lno+1, rlines);
+               for (i = 0; i <= num_parent; i++) putchar(combine_marker);
+               putchar('\n');
+               while (lno < hunk_end) {
+                       struct lline *ll;
+                       int j;
+                       unsigned long p_mask;
+                       sl = &sline[lno++];
+                       ll = sl->lost_head;
+                       while (ll) {
+                               for (j = 0; j < num_parent; j++) {
+                                       if (ll->parent_map & (1UL<<j))
+                                               putchar('-');
+                                       else
+                                               putchar(' ');
+                               }
+                               puts(ll->line);
+                               ll = ll->next;
+                       }
+                       if (cnt < lno)
+                               break;
+                       p_mask = 1;
+                       for (j = 0; j < num_parent; j++) {
+                               if (p_mask & sl->flag)
+                                       putchar('+');
+                               else
+                                       putchar(' ');
+                               p_mask <<= 1;
+                       }
+                       printf("%.*s\n", sl->len, sl->bol);
+               }
+       }
+}
+
+static void reuse_combine_diff(struct sline *sline, unsigned long cnt,
+                              int i, int j)
+{
+       /* We have already examined parent j and we know parent i
+        * and parent j are the same, so reuse the combined result
+        * of parent j for parent i.
+        */
+       unsigned long lno, imask, jmask;
+       imask = (1UL<<i);
+       jmask = (1UL<<j);
+
+       for (lno = 0; lno <= cnt; lno++) {
+               struct lline *ll = sline->lost_head;
+               sline->p_lno[i] = sline->p_lno[j];
+               while (ll) {
+                       if (ll->parent_map & jmask)
+                               ll->parent_map |= imask;
+                       ll = ll->next;
+               }
+               if (sline->flag & jmask)
+                       sline->flag |= imask;
+               sline++;
+       }
+       /* the overall size of the file (sline[cnt]) */
+       sline->p_lno[i] = sline->p_lno[j];
+}
+
+static void dump_quoted_path(const char *prefix, const char *path)
+{
+       fputs(prefix, stdout);
+       if (quote_c_style(path, NULL, NULL, 0))
+               quote_c_style(path, NULL, stdout, 0);
+       else
+               printf("%s", path);
+       putchar('\n');
+}
+
+static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
+                          int dense, struct rev_info *rev)
+{
+       struct diff_options *opt = &rev->diffopt;
+       unsigned long result_size, cnt, lno;
+       char *result, *cp;
+       struct sline *sline; /* survived lines */
+       int mode_differs = 0;
+       int i, show_hunks, shown_header = 0;
+       int working_tree_file = !memcmp(elem->sha1, null_sha1, 20);
+       int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV;
+       mmfile_t result_file;
+
+       context = opt->context;
+       /* Read the result of merge first */
+       if (!working_tree_file)
+               result = grab_blob(elem->sha1, &result_size);
+       else {
+               /* Used by diff-tree to read from the working tree */
+               struct stat st;
+               int fd;
+               if (0 <= (fd = open(elem->path, O_RDONLY)) &&
+                   !fstat(fd, &st)) {
+                       int len = st.st_size;
+                       int cnt = 0;
+
+                       elem->mode = canon_mode(st.st_mode);
+                       result_size = len;
+                       result = xmalloc(len + 1);
+                       while (cnt < len) {
+                               int done = xread(fd, result+cnt, len-cnt);
+                               if (done == 0)
+                                       break;
+                               if (done < 0)
+                                       die("read error '%s'", elem->path);
+                               cnt += done;
+                       }
+                       result[len] = 0;
+               }
+               else {
+                       /* deleted file */
+                       result_size = 0;
+                       elem->mode = 0;
+                       result = xmalloc(1);
+                       result[0] = 0;
+               }
+               if (0 <= fd)
+                       close(fd);
+       }
+
+       for (cnt = 0, cp = result; cp - result < result_size; cp++) {
+               if (*cp == '\n')
+                       cnt++;
+       }
+       if (result_size && result[result_size-1] != '\n')
+               cnt++; /* incomplete line */
+
+       sline = xcalloc(cnt+2, sizeof(*sline));
+       sline[0].bol = result;
+       for (lno = 0; lno <= cnt + 1; lno++) {
+               sline[lno].lost_tail = &sline[lno].lost_head;
+               sline[lno].flag = 0;
+       }
+       for (lno = 0, cp = result; cp - result < result_size; cp++) {
+               if (*cp == '\n') {
+                       sline[lno].len = cp - sline[lno].bol;
+                       lno++;
+                       if (lno < cnt)
+                               sline[lno].bol = cp + 1;
+               }
+       }
+       if (result_size && result[result_size-1] != '\n')
+               sline[cnt-1].len = result_size - (sline[cnt-1].bol - result);
+
+       result_file.ptr = result;
+       result_file.size = result_size;
+
+       /* Even p_lno[cnt+1] is valid -- that is for the end line number
+        * for deletion hunk at the end.
+        */
+       sline[0].p_lno = xcalloc((cnt+2) * num_parent, sizeof(unsigned long));
+       for (lno = 0; lno <= cnt; lno++)
+               sline[lno+1].p_lno = sline[lno].p_lno + num_parent;
+
+       for (i = 0; i < num_parent; i++) {
+               int j;
+               for (j = 0; j < i; j++) {
+                       if (!memcmp(elem->parent[i].sha1,
+                                   elem->parent[j].sha1, 20)) {
+                               reuse_combine_diff(sline, cnt, i, j);
+                               break;
+                       }
+               }
+               if (i <= j)
+                       combine_diff(elem->parent[i].sha1, &result_file, sline,
+                                    cnt, i, num_parent);
+               if (elem->parent[i].mode != elem->mode)
+                       mode_differs = 1;
+       }
+
+       show_hunks = make_hunks(sline, cnt, num_parent, dense);
+
+       if (show_hunks || mode_differs || working_tree_file) {
+               const char *abb;
+
+               if (rev->loginfo)
+                       show_log(rev, rev->loginfo, "\n");
+               dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
+               printf("index ");
+               for (i = 0; i < num_parent; i++) {
+                       abb = find_unique_abbrev(elem->parent[i].sha1,
+                                                abbrev);
+                       printf("%s%s", i ? "," : "", abb);
+               }
+               abb = find_unique_abbrev(elem->sha1, abbrev);
+               printf("..%s\n", abb);
+
+               if (mode_differs) {
+                       int added = !!elem->mode;
+                       for (i = 0; added && i < num_parent; i++)
+                               if (elem->parent[i].status !=
+                                   DIFF_STATUS_ADDED)
+                                       added = 0;
+                       if (added)
+                               printf("new file mode %06o", elem->mode);
+                       else {
+                               if (!elem->mode)
+                                       printf("deleted file ");
+                               printf("mode ");
+                               for (i = 0; i < num_parent; i++) {
+                                       printf("%s%06o", i ? "," : "",
+                                              elem->parent[i].mode);
+                               }
+                               if (elem->mode)
+                                       printf("..%06o", elem->mode);
+                       }
+                       putchar('\n');
+               }
+               dump_quoted_path("--- a/", elem->path);
+               dump_quoted_path("+++ b/", elem->path);
+               dump_sline(sline, cnt, num_parent);
+       }
+       free(result);
+
+       for (i = 0; i < cnt; i++) {
+               if (sline[i].lost_head) {
+                       struct lline *ll = sline[i].lost_head;
+                       while (ll) {
+                               struct lline *tmp = ll;
+                               ll = ll->next;
+                               free(tmp);
+                       }
+               }
+       }
+       free(sline[0].p_lno);
+       free(sline);
+       return shown_header;
+}
+
+#define COLONS "::::::::::::::::::::::::::::::::"
+
+static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev)
+{
+       struct diff_options *opt = &rev->diffopt;
+       int i, offset;
+       const char *prefix;
+       int line_termination, inter_name_termination;
+
+       line_termination = opt->line_termination;
+       inter_name_termination = '\t';
+       if (!line_termination)
+               inter_name_termination = 0;
+
+       if (rev->loginfo)
+               show_log(rev, rev->loginfo, "\n");
+
+       if (opt->output_format == DIFF_FORMAT_RAW) {
+               offset = strlen(COLONS) - num_parent;
+               if (offset < 0)
+                       offset = 0;
+               prefix = COLONS + offset;
+
+               /* Show the modes */
+               for (i = 0; i < num_parent; i++) {
+                       printf("%s%06o", prefix, p->parent[i].mode);
+                       prefix = " ";
+               }
+               printf("%s%06o", prefix, p->mode);
+
+               /* Show sha1's */
+               for (i = 0; i < num_parent; i++)
+                       printf(" %s", diff_unique_abbrev(p->parent[i].sha1,
+                                                        opt->abbrev));
+               printf(" %s ", diff_unique_abbrev(p->sha1, opt->abbrev));
+       }
+
+       if (opt->output_format == DIFF_FORMAT_RAW ||
+           opt->output_format == DIFF_FORMAT_NAME_STATUS) {
+               for (i = 0; i < num_parent; i++)
+                       putchar(p->parent[i].status);
+               putchar(inter_name_termination);
+       }
+
+       if (line_termination) {
+               if (quote_c_style(p->path, NULL, NULL, 0))
+                       quote_c_style(p->path, NULL, stdout, 0);
+               else
+                       printf("%s", p->path);
+               putchar(line_termination);
+       }
+       else {
+               printf("%s%c", p->path, line_termination);
+       }
+}
+
+void show_combined_diff(struct combine_diff_path *p,
+                      int num_parent,
+                      int dense,
+                      struct rev_info *rev)
+{
+       struct diff_options *opt = &rev->diffopt;
+       if (!p->len)
+               return;
+       switch (opt->output_format) {
+       case DIFF_FORMAT_RAW:
+       case DIFF_FORMAT_NAME_STATUS:
+       case DIFF_FORMAT_NAME:
+               show_raw_diff(p, num_parent, rev);
+               return;
+       case DIFF_FORMAT_PATCH:
+               show_patch_diff(p, num_parent, dense, rev);
+               return;
+       default:
+               return;
+       }
+}
+
+void diff_tree_combined(const unsigned char *sha1,
+                       const unsigned char parent[][20],
+                       int num_parent,
+                       int dense,
+                       struct rev_info *rev)
+{
+       struct diff_options *opt = &rev->diffopt;
+       struct diff_options diffopts;
+       struct combine_diff_path *p, *paths = NULL;
+       int i, num_paths;
+       int do_diffstat;
+
+       do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
+                      opt->with_stat);
+       diffopts = *opt;
+       diffopts.with_raw = 0;
+       diffopts.with_stat = 0;
+       diffopts.recursive = 1;
+
+       /* find set of paths that everybody touches */
+       for (i = 0; i < num_parent; i++) {
+               /* show stat against the first parent even
+                * when doing combined diff.
+                */
+               if (i == 0 && do_diffstat)
+                       diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+               else
+                       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
+               diff_tree_sha1(parent[i], sha1, "", &diffopts);
+               diffcore_std(&diffopts);
+               paths = intersect_paths(paths, i, num_parent);
+
+               if (do_diffstat && rev->loginfo)
+                       show_log(rev, rev->loginfo,
+                                opt->with_stat ? "---\n" : "\n");
+               diff_flush(&diffopts);
+               if (opt->with_stat)
+                       putchar('\n');
+       }
+
+       /* find out surviving paths */
+       for (num_paths = 0, p = paths; p; p = p->next) {
+               if (p->len)
+                       num_paths++;
+       }
+       if (num_paths) {
+               if (opt->with_raw) {
+                       int saved_format = opt->output_format;
+                       opt->output_format = DIFF_FORMAT_RAW;
+                       for (p = paths; p; p = p->next) {
+                               show_combined_diff(p, num_parent, dense, rev);
+                       }
+                       opt->output_format = saved_format;
+                       putchar(opt->line_termination);
+               }
+               for (p = paths; p; p = p->next) {
+                       show_combined_diff(p, num_parent, dense, rev);
+               }
+       }
+
+       /* Clean things up */
+       while (paths) {
+               struct combine_diff_path *tmp = paths;
+               paths = paths->next;
+               free(tmp);
+       }
+}
+
+void diff_tree_combined_merge(const unsigned char *sha1,
+                            int dense, struct rev_info *rev)
+{
+       int num_parent;
+       const unsigned char (*parent)[20];
+       struct commit *commit = lookup_commit(sha1);
+       struct commit_list *parents;
+
+       /* count parents */
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               ; /* nothing */
+
+       parent = xmalloc(num_parent * sizeof(*parent));
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               memcpy(parent + num_parent, parents->item->object.sha1, 20);
+       diff_tree_combined(sha1, parent, num_parent, dense, rev);
+}
diff --git a/commit.c b/commit.c
new file mode 100644 (file)
index 0000000..94f470b
--- /dev/null
+++ b/commit.c
@@ -0,0 +1,831 @@
+#include "cache.h"
+#include "tag.h"
+#include "commit.h"
+
+int save_commit_buffer = 1;
+
+struct sort_node
+{
+       /*
+         * the number of children of the associated commit
+         * that also occur in the list being sorted.
+         */
+       unsigned int indegree;
+
+       /*
+         * reference to original list item that we will re-use
+         * on output.
+         */
+       struct commit_list * list_item;
+
+};
+
+const char *commit_type = "commit";
+
+struct cmt_fmt_map {
+       const char *n;
+       size_t cmp_len;
+       enum cmit_fmt v;
+} cmt_fmts[] = {
+       { "raw",        1,      CMIT_FMT_RAW },
+       { "medium",     1,      CMIT_FMT_MEDIUM },
+       { "short",      1,      CMIT_FMT_SHORT },
+       { "email",      1,      CMIT_FMT_EMAIL },
+       { "full",       5,      CMIT_FMT_FULL },
+       { "fuller",     5,      CMIT_FMT_FULLER },
+       { "oneline",    1,      CMIT_FMT_ONELINE },
+};
+
+enum cmit_fmt get_commit_format(const char *arg)
+{
+       int i;
+
+       if (!arg || !*arg)
+               return CMIT_FMT_DEFAULT;
+       if (*arg == '=')
+               arg++;
+       for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
+               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len))
+                       return cmt_fmts[i].v;
+       }
+
+       die("invalid --pretty format: %s", arg);
+}
+
+static struct commit *check_commit(struct object *obj,
+                                  const unsigned char *sha1,
+                                  int quiet)
+{
+       if (obj->type != commit_type) {
+               if (!quiet)
+                       error("Object %s is a %s, not a commit",
+                             sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct commit *) obj;
+}
+
+struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
+                                             int quiet)
+{
+       struct object *obj = deref_tag(parse_object(sha1), NULL, 0);
+
+       if (!obj)
+               return NULL;
+       return check_commit(obj, sha1, quiet);
+}
+
+struct commit *lookup_commit_reference(const unsigned char *sha1)
+{
+       return lookup_commit_reference_gently(sha1, 0);
+}
+
+struct commit *lookup_commit(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct commit *ret = xcalloc(1, sizeof(struct commit));
+               created_object(sha1, &ret->object);
+               ret->object.type = commit_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = commit_type;
+       return check_commit(obj, sha1, 0);
+}
+
+static unsigned long parse_commit_date(const char *buf)
+{
+       unsigned long date;
+
+       if (memcmp(buf, "author", 6))
+               return 0;
+       while (*buf++ != '\n')
+               /* nada */;
+       if (memcmp(buf, "committer", 9))
+               return 0;
+       while (*buf++ != '>')
+               /* nada */;
+       date = strtoul(buf, NULL, 10);
+       if (date == ULONG_MAX)
+               date = 0;
+       return date;
+}
+
+static struct commit_graft **commit_graft;
+static int commit_graft_alloc, commit_graft_nr;
+
+static int commit_graft_pos(const unsigned char *sha1)
+{
+       int lo, hi;
+       lo = 0;
+       hi = commit_graft_nr;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct commit_graft *graft = commit_graft[mi];
+               int cmp = memcmp(sha1, graft->sha1, 20);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo - 1;
+}
+
+int register_commit_graft(struct commit_graft *graft, int ignore_dups)
+{
+       int pos = commit_graft_pos(graft->sha1);
+       
+       if (0 <= pos) {
+               if (ignore_dups)
+                       free(graft);
+               else {
+                       free(commit_graft[pos]);
+                       commit_graft[pos] = graft;
+               }
+               return 1;
+       }
+       pos = -pos - 1;
+       if (commit_graft_alloc <= ++commit_graft_nr) {
+               commit_graft_alloc = alloc_nr(commit_graft_alloc);
+               commit_graft = xrealloc(commit_graft,
+                                       sizeof(*commit_graft) *
+                                       commit_graft_alloc);
+       }
+       if (pos < commit_graft_nr)
+               memmove(commit_graft + pos + 1,
+                       commit_graft + pos,
+                       (commit_graft_nr - pos - 1) *
+                       sizeof(*commit_graft));
+       commit_graft[pos] = graft;
+       return 0;
+}
+
+struct commit_graft *read_graft_line(char *buf, int len)
+{
+       /* The format is just "Commit Parent1 Parent2 ...\n" */
+       int i;
+       struct commit_graft *graft = NULL;
+
+       if (buf[len-1] == '\n')
+               buf[--len] = 0;
+       if (buf[0] == '#' || buf[0] == '\0')
+               return NULL;
+       if ((len + 1) % 41) {
+       bad_graft_data:
+               error("bad graft data: %s", buf);
+               free(graft);
+               return NULL;
+       }
+       i = (len + 1) / 41 - 1;
+       graft = xmalloc(sizeof(*graft) + 20 * i);
+       graft->nr_parent = i;
+       if (get_sha1_hex(buf, graft->sha1))
+               goto bad_graft_data;
+       for (i = 40; i < len; i += 41) {
+               if (buf[i] != ' ')
+                       goto bad_graft_data;
+               if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+                       goto bad_graft_data;
+       }
+       return graft;
+}
+
+int read_graft_file(const char *graft_file)
+{
+       FILE *fp = fopen(graft_file, "r");
+       char buf[1024];
+       if (!fp)
+               return -1;
+       while (fgets(buf, sizeof(buf), fp)) {
+               /* The format is just "Commit Parent1 Parent2 ...\n" */
+               int len = strlen(buf);
+               struct commit_graft *graft = read_graft_line(buf, len);
+               if (!graft)
+                       continue;
+               if (register_commit_graft(graft, 1))
+                       error("duplicate graft data: %s", buf);
+       }
+       fclose(fp);
+       return 0;
+}
+
+static void prepare_commit_graft(void)
+{
+       static int commit_graft_prepared;
+       char *graft_file;
+
+       if (commit_graft_prepared)
+               return;
+       graft_file = get_graft_file();
+       read_graft_file(graft_file);
+       commit_graft_prepared = 1;
+}
+
+static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+{
+       int pos;
+       prepare_commit_graft();
+       pos = commit_graft_pos(sha1);
+       if (pos < 0)
+               return NULL;
+       return commit_graft[pos];
+}
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+{
+       char *bufptr = buffer;
+       unsigned char parent[20];
+       struct commit_list **pptr;
+       struct commit_graft *graft;
+       unsigned n_refs = 0;
+
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
+       if (memcmp(bufptr, "tree ", 5))
+               return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
+       if (get_sha1_hex(bufptr + 5, parent) < 0)
+               return error("bad tree pointer in commit %s",
+                            sha1_to_hex(item->object.sha1));
+       item->tree = lookup_tree(parent);
+       if (item->tree)
+               n_refs++;
+       bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+       pptr = &item->parents;
+
+       graft = lookup_commit_graft(item->object.sha1);
+       while (!memcmp(bufptr, "parent ", 7)) {
+               struct commit *new_parent;
+
+               if (get_sha1_hex(bufptr + 7, parent) || bufptr[47] != '\n')
+                       return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
+               bufptr += 48;
+               if (graft)
+                       continue;
+               new_parent = lookup_commit(parent);
+               if (new_parent) {
+                       pptr = &commit_list_insert(new_parent, pptr)->next;
+                       n_refs++;
+               }
+       }
+       if (graft) {
+               int i;
+               struct commit *new_parent;
+               for (i = 0; i < graft->nr_parent; i++) {
+                       new_parent = lookup_commit(graft->parent[i]);
+                       if (!new_parent)
+                               continue;
+                       pptr = &commit_list_insert(new_parent, pptr)->next;
+                       n_refs++;
+               }
+       }
+       item->date = parse_commit_date(bufptr);
+
+       if (track_object_refs) {
+               unsigned i = 0;
+               struct commit_list *p;
+               struct object_refs *refs = alloc_object_refs(n_refs);
+               if (item->tree)
+                       refs->ref[i++] = &item->tree->object;
+               for (p = item->parents; p; p = p->next)
+                       refs->ref[i++] = &p->item->object;
+               set_object_refs(&item->object, refs);
+       }
+
+       return 0;
+}
+
+int parse_commit(struct commit *item)
+{
+       char type[20];
+       void *buffer;
+       unsigned long size;
+       int ret;
+
+       if (item->object.parsed)
+               return 0;
+       buffer = read_sha1_file(item->object.sha1, type, &size);
+       if (!buffer)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, commit_type)) {
+               free(buffer);
+               return error("Object %s not a commit",
+                            sha1_to_hex(item->object.sha1));
+       }
+       ret = parse_commit_buffer(item, buffer, size);
+       if (save_commit_buffer && !ret) {
+               item->buffer = buffer;
+               return 0;
+       }
+       free(buffer);
+       return ret;
+}
+
+struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
+{
+       struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
+       new_list->item = item;
+       new_list->next = *list_p;
+       *list_p = new_list;
+       return new_list;
+}
+
+void free_commit_list(struct commit_list *list)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+{
+       struct commit_list **pp = list;
+       struct commit_list *p;
+       while ((p = *pp) != NULL) {
+               if (p->item->date < item->date) {
+                       break;
+               }
+               pp = &p->next;
+       }
+       return commit_list_insert(item, pp);
+}
+
+       
+void sort_by_date(struct commit_list **list)
+{
+       struct commit_list *ret = NULL;
+       while (*list) {
+               insert_by_date((*list)->item, &ret);
+               *list = (*list)->next;
+       }
+       *list = ret;
+}
+
+struct commit *pop_most_recent_commit(struct commit_list **list,
+                                     unsigned int mark)
+{
+       struct commit *ret = (*list)->item;
+       struct commit_list *parents = ret->parents;
+       struct commit_list *old = *list;
+
+       *list = (*list)->next;
+       free(old);
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               parse_commit(commit);
+               if (!(commit->object.flags & mark)) {
+                       commit->object.flags |= mark;
+                       insert_by_date(commit, list);
+               }
+               parents = parents->next;
+       }
+       return ret;
+}
+
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+       struct commit_list *parents;
+
+       parents = commit->parents;
+       commit->object.flags &= ~mark;
+       while (parents) {
+               struct commit *parent = parents->item;
+               if (parent && parent->object.parsed &&
+                   (parent->object.flags & mark))
+                       clear_commit_marks(parent, mark);
+               parents = parents->next;
+       }
+}
+
+/*
+ * Generic support for pretty-printing the header
+ */
+static int get_one_line(const char *msg, unsigned long len)
+{
+       int ret = 0;
+
+       while (len--) {
+               char c = *msg++;
+               if (!c)
+                       break;
+               ret++;
+               if (c == '\n')
+                       break;
+       }
+       return ret;
+}
+
+static int is_rfc2047_special(char ch)
+{
+       return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_'));
+}
+
+static int add_rfc2047(char *buf, const char *line, int len)
+{
+       char *bp = buf;
+       int i, needquote;
+       static const char q_utf8[] = "=?utf-8?q?";
+
+       for (i = needquote = 0; !needquote && i < len; i++) {
+               unsigned ch = line[i];
+               if (ch & 0x80)
+                       needquote++;
+               if ((i + 1 < len) &&
+                   (ch == '=' && line[i+1] == '?'))
+                       needquote++;
+       }
+       if (!needquote)
+               return sprintf(buf, "%.*s", len, line);
+
+       memcpy(bp, q_utf8, sizeof(q_utf8)-1);
+       bp += sizeof(q_utf8)-1;
+       for (i = 0; i < len; i++) {
+               unsigned ch = line[i];
+               if (is_rfc2047_special(ch)) {
+                       sprintf(bp, "=%02X", ch);
+                       bp += 3;
+               }
+               else if (ch == ' ')
+                       *bp++ = '_';
+               else
+                       *bp++ = ch;
+       }
+       memcpy(bp, "?=", 2);
+       bp += 2;
+       return bp - buf;
+}
+
+static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
+{
+       char *date;
+       int namelen;
+       unsigned long time;
+       int tz, ret;
+       const char *filler = "    ";
+
+       if (fmt == CMIT_FMT_ONELINE)
+               return 0;
+       date = strchr(line, '>');
+       if (!date)
+               return 0;
+       namelen = ++date - line;
+       time = strtoul(date, &date, 10);
+       tz = strtol(date, NULL, 10);
+
+       if (fmt == CMIT_FMT_EMAIL) {
+               char *name_tail = strchr(line, '<');
+               int display_name_length;
+               if (!name_tail)
+                       return 0;
+               while (line < name_tail && isspace(name_tail[-1]))
+                       name_tail--;
+               display_name_length = name_tail - line;
+               filler = "";
+               strcpy(buf, "From: ");
+               ret = strlen(buf);
+               ret += add_rfc2047(buf + ret, line, display_name_length);
+               memcpy(buf + ret, name_tail, namelen - display_name_length);
+               ret += namelen - display_name_length;
+               buf[ret++] = '\n';
+       }
+       else {
+               ret = sprintf(buf, "%s: %.*s%.*s\n", what,
+                             (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+                             filler, namelen, line);
+       }
+       switch (fmt) {
+       case CMIT_FMT_MEDIUM:
+               ret += sprintf(buf + ret, "Date:   %s\n", show_date(time, tz));
+               break;
+       case CMIT_FMT_EMAIL:
+               ret += sprintf(buf + ret, "Date: %s\n",
+                              show_rfc2822_date(time, tz));
+               break;
+       case CMIT_FMT_FULLER:
+               ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
+               break;
+       default:
+               /* notin' */
+               break;
+       }
+       return ret;
+}
+
+static int is_empty_line(const char *line, int *len_p)
+{
+       int len = *len_p;
+       while (len && isspace(line[len-1]))
+               len--;
+       *len_p = len;
+       return !len;
+}
+
+static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev)
+{
+       struct commit_list *parent = commit->parents;
+       int offset;
+
+       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+           !parent || !parent->next)
+               return 0;
+
+       offset = sprintf(buf, "Merge:");
+
+       while (parent) {
+               struct commit *p = parent->item;
+               const char *hex = abbrev
+                       ? find_unique_abbrev(p->object.sha1, abbrev)
+                       : sha1_to_hex(p->object.sha1);
+               char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
+               parent = parent->next;
+
+               offset += sprintf(buf + offset, " %s%s", hex, dots);
+       }
+       buf[offset++] = '\n';
+       return offset;
+}
+
+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 hdr = 1, body = 0;
+       unsigned long offset = 0;
+       int indent = 4;
+       int parents_shown = 0;
+       const char *msg = commit->buffer;
+       int plain_non_ascii = 0;
+
+       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+               indent = 0;
+
+       /* After-subject is used to pass in Content-Type: multipart
+        * MIME header; in that case we do not have to do the
+        * plaintext content type even if the commit message has
+        * non 7-bit ASCII character.  Otherwise, check if we need
+        * to say this is not a 7-bit ASCII.
+        */
+       if (fmt == CMIT_FMT_EMAIL && !after_subject) {
+               int i;
+               for (i = 0; !plain_non_ascii && msg[i] && i < len; i++)
+                       if (msg[i] & 0x80)
+                               plain_non_ascii = 1;
+       }
+
+       for (;;) {
+               const char *line = msg;
+               int linelen = get_one_line(msg, len);
+
+               if (!linelen)
+                       break;
+
+               /*
+                * We want some slop for indentation and a possible
+                * final "...". Thus the "+ 20".
+                */
+               if (offset + linelen + 20 > space) {
+                       memcpy(buf + offset, "    ...\n", 8);
+                       offset += 8;
+                       break;
+               }
+
+               msg += linelen;
+               len -= linelen;
+               if (hdr) {
+                       if (linelen == 1) {
+                               hdr = 0;
+                               if ((fmt != CMIT_FMT_ONELINE) && !subject)
+                                       buf[offset++] = '\n';
+                               continue;
+                       }
+                       if (fmt == CMIT_FMT_RAW) {
+                               memcpy(buf + offset, line, linelen);
+                               offset += linelen;
+                               continue;
+                       }
+                       if (!memcmp(line, "parent ", 7)) {
+                               if (linelen != 48)
+                                       die("bad parent line in commit");
+                               continue;
+                       }
+
+                       if (!parents_shown) {
+                               offset += add_merge_info(fmt, buf + offset,
+                                                        commit, abbrev);
+                               parents_shown = 1;
+                               continue;
+                       }
+                       /*
+                        * MEDIUM == DEFAULT shows only author with dates.
+                        * FULL shows both authors but not dates.
+                        * FULLER shows both authors and dates.
+                        */
+                       if (!memcmp(line, "author ", 7))
+                               offset += add_user_info("Author", fmt,
+                                                       buf + offset,
+                                                       line + 7);
+                       if (!memcmp(line, "committer ", 10) &&
+                           (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
+                               offset += add_user_info("Commit", fmt,
+                                                       buf + offset,
+                                                       line + 10);
+                       continue;
+               }
+
+               if (is_empty_line(line, &linelen)) {
+                       if (!body)
+                               continue;
+                       if (subject)
+                               continue;
+                       if (fmt == CMIT_FMT_SHORT)
+                               break;
+               } else {
+                       body = 1;
+               }
+
+               if (subject) {
+                       int slen = strlen(subject);
+                       memcpy(buf + offset, subject, slen);
+                       offset += slen;
+                       offset += add_rfc2047(buf + offset, line, linelen);
+               }
+               else {
+                       memset(buf + offset, ' ', indent);
+                       memcpy(buf + offset + indent, line, linelen);
+                       offset += linelen + indent;
+               }
+               buf[offset++] = '\n';
+               if (fmt == CMIT_FMT_ONELINE)
+                       break;
+               if (subject && plain_non_ascii) {
+                       static const char header[] =
+                               "Content-Type: text/plain; charset=UTF-8\n"
+                               "Content-Transfer-Encoding: 8bit\n";
+                       memcpy(buf + offset, header, sizeof(header)-1);
+                       offset += sizeof(header)-1;
+               }
+               if (after_subject) {
+                       int slen = strlen(after_subject);
+                       if (slen > space - offset - 1)
+                               slen = space - offset - 1;
+                       memcpy(buf + offset, after_subject, slen);
+                       offset += slen;
+                       after_subject = NULL;
+               }
+               subject = NULL;
+       }
+       while (offset && isspace(buf[offset-1]))
+               offset--;
+       /* Make sure there is an EOLN for the non-oneline case */
+       if (fmt != CMIT_FMT_ONELINE)
+               buf[offset++] = '\n';
+       buf[offset] = '\0';
+       return offset;
+}
+
+struct commit *pop_commit(struct commit_list **stack)
+{
+       struct commit_list *top = *stack;
+       struct commit *item = top ? top->item : NULL;
+
+       if (top) {
+               *stack = top->next;
+               free(top);
+       }
+       return item;
+}
+
+int count_parents(struct commit * commit)
+{
+        int count = 0;
+        struct commit_list * parents = commit->parents;
+        for (count=0;parents; parents=parents->next,count++)
+          ;
+        return count;
+}
+
+void topo_sort_default_setter(struct commit *c, void *data)
+{
+       c->object.util = data;
+}
+
+void *topo_sort_default_getter(struct commit *c)
+{
+       return c->object.util;
+}
+
+/*
+ * Performs an in-place topological sort on the list supplied.
+ */
+void sort_in_topological_order(struct commit_list ** list, int lifo)
+{
+       sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
+                                    topo_sort_default_getter);
+}
+
+void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
+                                 topo_sort_set_fn_t setter,
+                                 topo_sort_get_fn_t getter)
+{
+       struct commit_list * next = *list;
+       struct commit_list * work = NULL, **insert;
+       struct commit_list ** pptr = list;
+       struct sort_node * nodes;
+       struct sort_node * next_nodes;
+       int count = 0;
+
+       /* determine the size of the list */
+       while (next) {
+               next = next->next;
+               count++;
+       }
+       
+       if (!count)
+               return;
+       /* allocate an array to help sort the list */
+       nodes = xcalloc(count, sizeof(*nodes));
+       /* link the list to the array */
+       next_nodes = nodes;
+       next=*list;
+       while (next) {
+               next_nodes->list_item = next;
+               setter(next->item, next_nodes);
+               next_nodes++;
+               next = next->next;
+       }
+       /* update the indegree */
+       next=*list;
+       while (next) {
+               struct commit_list * parents = next->item->parents;
+               while (parents) {
+                       struct commit * parent=parents->item;
+                       struct sort_node * pn = (struct sort_node *) getter(parent);
+
+                       if (pn)
+                               pn->indegree++;
+                       parents=parents->next;
+               }
+               next=next->next;
+       }
+       /* 
+         * find the tips
+         *
+         * tips are nodes not reachable from any other node in the list 
+         * 
+         * the tips serve as a starting set for the work queue.
+         */
+       next=*list;
+       insert = &work;
+       while (next) {
+               struct sort_node * node = (struct sort_node *) getter(next->item);
+
+               if (node->indegree == 0) {
+                       insert = &commit_list_insert(next->item, insert)->next;
+               }
+               next=next->next;
+       }
+
+       /* process the list in topological order */
+       if (!lifo)
+               sort_by_date(&work);
+       while (work) {
+               struct commit * work_item = pop_commit(&work);
+               struct sort_node * work_node = (struct sort_node *) getter(work_item);
+               struct commit_list * parents = work_item->parents;
+
+               while (parents) {
+                       struct commit * parent=parents->item;
+                       struct sort_node * pn = (struct sort_node *) getter(parent);
+
+                       if (pn) {
+                               /*
+                                * parents are only enqueued for emission 
+                                 * when all their children have been emitted thereby
+                                 * guaranteeing topological order.
+                                 */
+                               pn->indegree--;
+                               if (!pn->indegree) {
+                                       if (!lifo)
+                                               insert_by_date(parent, &work);
+                                       else
+                                               commit_list_insert(parent, &work);
+                               }
+                       }
+                       parents=parents->next;
+               }
+               /*
+                 * work_item is a commit all of whose children
+                 * have already been emitted. we can emit it now.
+                 */
+               *pptr = work_node->list_item;
+               pptr = &(*pptr)->next;
+               *pptr = NULL;
+               setter(work_item, NULL);
+       }
+       free(nodes);
+}
diff --git a/commit.h b/commit.h
new file mode 100644 (file)
index 0000000..c9de167
--- /dev/null
+++ b/commit.h
@@ -0,0 +1,107 @@
+#ifndef COMMIT_H
+#define COMMIT_H
+
+#include "object.h"
+#include "tree.h"
+
+struct commit_list {
+       struct commit *item;
+       struct commit_list *next;
+};
+
+struct commit {
+       struct object object;
+       unsigned long date;
+       struct commit_list *parents;
+       struct tree *tree;
+       char *buffer;
+};
+
+extern int save_commit_buffer;
+extern const char *commit_type;
+
+struct commit *lookup_commit(const unsigned char *sha1);
+struct commit *lookup_commit_reference(const unsigned char *sha1);
+struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
+                                             int quiet);
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
+
+int parse_commit(struct commit *item);
+
+struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
+
+void free_commit_list(struct commit_list *list);
+
+void sort_by_date(struct commit_list **list);
+
+/* Commit formats */
+enum cmit_fmt {
+       CMIT_FMT_RAW,
+       CMIT_FMT_MEDIUM,
+       CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
+       CMIT_FMT_SHORT,
+       CMIT_FMT_FULL,
+       CMIT_FMT_FULLER,
+       CMIT_FMT_ONELINE,
+       CMIT_FMT_EMAIL,
+
+       CMIT_FMT_UNSPECIFIED,
+};
+
+extern enum cmit_fmt get_commit_format(const char *arg);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject);
+
+/** Removes the first commit from a list sorted by date, and adds all
+ * of its parents.
+ **/
+struct commit *pop_most_recent_commit(struct commit_list **list, 
+                                     unsigned int mark);
+
+struct commit *pop_commit(struct commit_list **stack);
+
+void clear_commit_marks(struct commit *commit, unsigned int mark);
+
+int count_parents(struct commit * commit);
+
+/*
+ * Performs an in-place topological sort of list supplied.
+ *
+ * Pre-conditions for sort_in_topological_order:
+ *   all commits in input list and all parents of those
+ *   commits must have object.util == NULL
+ *
+ * Pre-conditions for sort_in_topological_order_fn:
+ *   all commits in input list and all parents of those
+ *   commits must have getter(commit) == NULL
+ *
+ * Post-conditions:
+ *   invariant of resulting list is:
+ *      a reachable from b => ord(b) < ord(a)
+ *   in addition, when lifo == 0, commits on parallel tracks are
+ *   sorted in the dates order.
+ */
+
+typedef void (*topo_sort_set_fn_t)(struct commit*, void *data);
+typedef void* (*topo_sort_get_fn_t)(struct commit*);
+
+void topo_sort_default_setter(struct commit *c, void *data);
+void *topo_sort_default_getter(struct commit *c);
+
+void sort_in_topological_order(struct commit_list ** list, int lifo);
+void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
+                                 topo_sort_set_fn_t setter,
+                                 topo_sort_get_fn_t getter);
+
+struct commit_graft {
+       unsigned char sha1[20];
+       int nr_parent;
+       unsigned char parent[FLEX_ARRAY][20]; /* more */
+};
+
+struct commit_graft *read_graft_line(char *buf, int len);
+int register_commit_graft(struct commit_graft *, int);
+int read_graft_file(const char *graft_file);
+
+#endif /* COMMIT_H */
diff --git a/compat/inet_ntop.c b/compat/inet_ntop.c
new file mode 100644 (file)
index 0000000..ec8c1bf
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef NS_INADDRSZ
+#define NS_INADDRSZ    4
+#endif
+#ifndef NS_IN6ADDRSZ
+#define NS_IN6ADDRSZ   16
+#endif
+#ifndef NS_INT16SZ
+#define NS_INT16SZ     2
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4.  sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+/* const char *
+ * inet_ntop4(src, dst, size)
+ *     format an IPv4 address
+ * return:
+ *     `dst' (as a const)
+ * notes:
+ *     (1) uses no statics
+ *     (2) takes a u_char* not an in_addr as input
+ * author:
+ *     Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop4(src, dst, size)
+       const u_char *src;
+       char *dst;
+       size_t size;
+{
+       static const char fmt[] = "%u.%u.%u.%u";
+       char tmp[sizeof "255.255.255.255"];
+       int nprinted;
+
+       nprinted = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
+       if (nprinted < 0)
+               return (NULL);  /* we assume "errno" was set by "snprintf()" */
+       if ((size_t)nprinted > size) {
+               errno = ENOSPC;
+               return (NULL);
+       }
+       strcpy(dst, tmp);
+       return (dst);
+}
+
+#ifndef NO_IPV6
+/* const char *
+ * inet_ntop6(src, dst, size)
+ *     convert IPv6 binary address into presentation (printable) format
+ * author:
+ *     Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop6(src, dst, size)
+       const u_char *src;
+       char *dst;
+       size_t size;
+{
+       /*
+        * Note that int32_t and int16_t need only be "at least" large enough
+        * to contain a value of the specified size.  On some systems, like
+        * Crays, there is no such thing as an integer variable with 16 bits.
+        * Keep this in mind if you think this function should have been coded
+        * to use pointer overlays.  All the world's not a VAX.
+        */
+       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];
+       int i;
+
+       /*
+        * Preprocess:
+        *      Copy the input (bytewise) array into a wordwise array.
+        *      Find the longest run of 0x00's in src[] for :: shorthanding.
+        */
+       memset(words, '\0', sizeof words);
+       for (i = 0; i < NS_IN6ADDRSZ; i++)
+               words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
+       best.base = -1;
+       cur.base = -1;
+       for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+               if (words[i] == 0) {
+                       if (cur.base == -1)
+                               cur.base = i, cur.len = 1;
+                       else
+                               cur.len++;
+               } else {
+                       if (cur.base != -1) {
+                               if (best.base == -1 || cur.len > best.len)
+                                       best = cur;
+                               cur.base = -1;
+                       }
+               }
+       }
+       if (cur.base != -1) {
+               if (best.base == -1 || cur.len > best.len)
+                       best = cur;
+       }
+       if (best.base != -1 && best.len < 2)
+               best.base = -1;
+
+       /*
+        * Format the result.
+        */
+       tp = tmp;
+       for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+               /* Are we inside the best run of 0x00's? */
+               if (best.base != -1 && i >= best.base &&
+                   i < (best.base + best.len)) {
+                       if (i == best.base)
+                               *tp++ = ':';
+                       continue;
+               }
+               /* Are we following an initial run of 0x00s or any real hex? */
+               if (i != 0)
+                       *tp++ = ':';
+               /* Is this address an encapsulated IPv4? */
+               if (i == 6 && best.base == 0 &&
+                   (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
+                       if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp)))
+                               return (NULL);
+                       tp += strlen(tp);
+                       break;
+               }
+               tp += snprintf(tp, sizeof tmp - (tp - tmp), "%x", words[i]);
+       }
+       /* Was it a trailing run of 0x00's? */
+       if (best.base != -1 && (best.base + best.len) ==
+           (NS_IN6ADDRSZ / NS_INT16SZ))
+               *tp++ = ':';
+       *tp++ = '\0';
+
+       /*
+        * Check for overflow, copy, and we're done.
+        */
+       if ((size_t)(tp - tmp) > size) {
+               errno = ENOSPC;
+               return (NULL);
+       }
+       strcpy(dst, tmp);
+       return (dst);
+}
+#endif
+
+/* char *
+ * inet_ntop(af, src, dst, size)
+ *     convert a network format address to presentation format.
+ * return:
+ *     pointer to presentation format address (`dst'), or NULL (see errno).
+ * author:
+ *     Paul Vixie, 1996.
+ */
+const char *
+inet_ntop(af, src, dst, size)
+       int af;
+       const void *src;
+       char *dst;
+       size_t size;
+{
+       switch (af) {
+       case AF_INET:
+               return (inet_ntop4(src, dst, size));
+#ifndef NO_IPV6
+       case AF_INET6:
+               return (inet_ntop6(src, dst, size));
+#endif
+       default:
+               errno = EAFNOSUPPORT;
+               return (NULL);
+       }
+       /* NOTREACHED */
+}
diff --git a/compat/mmap.c b/compat/mmap.c
new file mode 100644 (file)
index 0000000..55cb120
--- /dev/null
@@ -0,0 +1,50 @@
+#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)
+{
+       int 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;
+       }
+
+       start = xmalloc(length);
+       if (start == NULL) {
+               errno = ENOMEM;
+               return MAP_FAILED;
+       }
+
+       while (n < length) {
+               int count = read(fd, start+n, length-n);
+
+               if (count == 0) {
+                       memset(start+n, 0, length-n);
+                       break;
+               }
+
+               if (count < 0) {
+                       free(start);
+                       errno = EACCES;
+                       return MAP_FAILED;
+               }
+
+               n += count;
+       }
+
+       return start;
+}
+
+int gitfakemunmap(void *start, size_t length)
+{
+       free(start);
+       return 0;
+}
+
diff --git a/compat/setenv.c b/compat/setenv.c
new file mode 100644 (file)
index 0000000..b7d7678
--- /dev/null
@@ -0,0 +1,35 @@
+#include <stdlib.h>
+#include <string.h>
+
+int gitsetenv(const char *name, const char *value, int replace)
+{
+       int out;
+       size_t namelen, valuelen;
+       char *envstr;
+
+       if (!name || !value) return -1;
+       if (!replace) {
+               char *oldval = NULL;
+               oldval = getenv(name);
+               if (oldval) return 0;
+       }
+
+       namelen = strlen(name);
+       valuelen = strlen(value);
+       envstr = malloc((namelen + valuelen + 2));
+       if (!envstr) return -1;
+
+       memcpy(envstr, name, namelen);
+       envstr[namelen] = '=';
+       memcpy(envstr + namelen + 1, value, valuelen);
+       envstr[namelen + valuelen + 1] = 0;
+
+       out = putenv(envstr);
+       /* putenv(3) makes the argument string part of the environment,
+        * and changing that string modifies the environment --- which
+        * means we do not own that storage anymore.  Do not free
+        * envstr.
+        */
+
+       return out;
+}
diff --git a/compat/strcasestr.c b/compat/strcasestr.c
new file mode 100644 (file)
index 0000000..26896de
--- /dev/null
@@ -0,0 +1,22 @@
+#include "../git-compat-util.h"
+
+char *gitstrcasestr(const char *haystack, const char *needle)
+{
+       int nlen = strlen(needle);
+       int hlen = strlen(haystack) - nlen + 1;
+       int i;
+
+       for (i = 0; i < hlen; i++) {
+               int j;
+               for (j = 0; j < nlen; j++) {
+                       unsigned char c1 = haystack[i+j];
+                       unsigned char c2 = needle[j];
+                       if (toupper(c1) != toupper(c2))
+                               goto next;
+               }
+               return (char *) haystack + i;
+       next:
+               ;
+       }
+       return NULL;
+}
diff --git a/compat/subprocess.py b/compat/subprocess.py
new file mode 100644 (file)
index 0000000..bbd26c7
--- /dev/null
@@ -0,0 +1,1149 @@
+# 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 tupel 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 absolut 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 tupel 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()
diff --git a/compat/unsetenv.c b/compat/unsetenv.c
new file mode 100644 (file)
index 0000000..3a5e4ec
--- /dev/null
@@ -0,0 +1,26 @@
+#include <stdlib.h>
+#include <string.h>
+
+void gitunsetenv (const char *name)
+{
+     extern char **environ;
+     int src, dst;
+     size_t nmln;
+
+     nmln = strlen(name);
+
+     for (src = dst = 0; environ[src]; ++src) {
+         size_t enln;
+         enln = strlen(environ[src]);
+         if (enln > nmln) {
+               /* might match, and can test for '=' safely */
+              if (0 == strncmp (environ[src], name, nmln)
+                  && '=' == environ[src][nmln])
+                   /* matches, so skip */
+                   continue;
+         }
+         environ[dst] = environ[src];
+         ++dst;
+     }
+     environ[dst] = NULL;
+}
diff --git a/config.c b/config.c
new file mode 100644 (file)
index 0000000..2ae6153
--- /dev/null
+++ b/config.c
@@ -0,0 +1,691 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Johannes Schindelin, 2005
+ *
+ */
+#include "cache.h"
+#include <regex.h>
+
+#define MAXNAME (256)
+
+static FILE *config_file;
+static const char *config_file_name;
+static int config_linenr;
+static int get_next_char(void)
+{
+       int c;
+       FILE *f;
+
+       c = '\n';
+       if ((f = config_file) != NULL) {
+               c = fgetc(f);
+               if (c == '\r') {
+                       /* DOS like systems */
+                       c = fgetc(f);
+                       if (c != '\n') {
+                               ungetc(c, f);
+                               c = '\r';
+                       }
+               }
+               if (c == '\n')
+                       config_linenr++;
+               if (c == EOF) {
+                       config_file = NULL;
+                       c = '\n';
+               }
+       }
+       return c;
+}
+
+static char *parse_value(void)
+{
+       static char value[1024];
+       int quote = 0, comment = 0, len = 0, space = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (len >= sizeof(value))
+                       return NULL;
+               if (c == '\n') {
+                       if (quote)
+                               return NULL;
+                       value[len] = 0;
+                       return value;
+               }
+               if (comment)
+                       continue;
+               if (isspace(c) && !quote) {
+                       space = 1;
+                       continue;
+               }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
+               if (space) {
+                       if (len)
+                               value[len++] = ' ';
+                       space = 0;
+               }
+               if (c == '\\') {
+                       c = get_next_char();
+                       switch (c) {
+                       case '\n':
+                               continue;
+                       case 't':
+                               c = '\t';
+                               break;
+                       case 'b':
+                               c = '\b';
+                               break;
+                       case 'n':
+                               c = '\n';
+                               break;
+                       /* Some characters escape as themselves */
+                       case '\\': case '"':
+                               break;
+                       /* Reject unknown escape sequences */
+                       default:
+                               return NULL;
+                       }
+                       value[len++] = c;
+                       continue;
+               }
+               if (c == '"') {
+                       quote = 1-quote;
+                       continue;
+               }
+               value[len++] = c;
+       }
+}
+
+static int get_value(config_fn_t fn, char *name, unsigned int len)
+{
+       int c;
+       char *value;
+
+       /* Get the full name */
+       for (;;) {
+               c = get_next_char();
+               if (c == EOF)
+                       break;
+               if (!isalnum(c))
+                       break;
+               name[len++] = tolower(c);
+               if (len >= MAXNAME)
+                       return -1;
+       }
+       name[len] = 0;
+       while (c == ' ' || c == '\t')
+               c = get_next_char();
+
+       value = NULL;
+       if (c != '\n') {
+               if (c != '=')
+                       return -1;
+               value = parse_value();
+               if (!value)
+                       return -1;
+       }
+       return fn(name, value);
+}
+
+static int get_extended_base_var(char *name, int baselen, int c)
+{
+       do {
+               if (c == '\n')
+                       return -1;
+               c = get_next_char();
+       } while (isspace(c));
+
+       /* We require the format to be '[base "extension"]' */
+       if (c != '"')
+               return -1;
+       name[baselen++] = '.';
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n')
+                       return -1;
+               if (c == '"')
+                       break;
+               if (c == '\\') {
+                       c = get_next_char();
+                       if (c == '\n')
+                               return -1;
+               }
+               name[baselen++] = c;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+       }
+
+       /* Final ']' */
+       if (get_next_char() != ']')
+               return -1;
+       return baselen;
+}
+
+static int get_base_var(char *name)
+{
+       int baselen = 0;
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == EOF)
+                       return -1;
+               if (c == ']')
+                       return baselen;
+               if (isspace(c))
+                       return get_extended_base_var(name, baselen, c);
+               if (!isalnum(c) && c != '.')
+                       return -1;
+               if (baselen > MAXNAME / 2)
+                       return -1;
+               name[baselen++] = tolower(c);
+       }
+}
+
+static int git_parse_file(config_fn_t fn)
+{
+       int comment = 0;
+       int baselen = 0;
+       static char var[MAXNAME];
+
+       for (;;) {
+               int c = get_next_char();
+               if (c == '\n') {
+                       /* EOF? */
+                       if (!config_file)
+                               return 0;
+                       comment = 0;
+                       continue;
+               }
+               if (comment || isspace(c))
+                       continue;
+               if (c == '#' || c == ';') {
+                       comment = 1;
+                       continue;
+               }
+               if (c == '[') {
+                       baselen = get_base_var(var);
+                       if (baselen <= 0)
+                               break;
+                       var[baselen++] = '.';
+                       var[baselen] = 0;
+                       continue;
+               }
+               if (!isalpha(c))
+                       break;
+               var[baselen] = tolower(c);
+               if (get_value(fn, var, baselen+1) < 0)
+                       break;
+       }
+       die("bad config file line %d in %s", config_linenr, config_file_name);
+}
+
+int git_config_int(const char *name, const char *value)
+{
+       if (value && *value) {
+               char *end;
+               int val = strtol(value, &end, 0);
+               if (!*end)
+                       return val;
+       }
+       die("bad config value for '%s' in %s", name, config_file_name);
+}
+
+int git_config_bool(const char *name, const char *value)
+{
+       if (!value)
+               return 1;
+       if (!*value)
+               return 0;
+       if (!strcasecmp(value, "true"))
+               return 1;
+       if (!strcasecmp(value, "false"))
+               return 0;
+       return git_config_int(name, value) != 0;
+}
+
+int git_default_config(const char *var, const char *value)
+{
+       /* This needs a better name */
+       if (!strcmp(var, "core.filemode")) {
+               trust_executable_bit = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.ignorestat")) {
+               assume_unchanged = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.prefersymlinkrefs")) {
+               prefer_symlink_refs = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.logallrefupdates")) {
+               log_all_ref_updates = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.warnambiguousrefs")) {
+               warn_ambiguous_refs = git_config_bool(var, value);
+               return 0;
+       }
+
+       if (!strcmp(var, "user.name")) {
+               strncpy(git_default_name, value, sizeof(git_default_name));
+               return 0;
+       }
+
+       if (!strcmp(var, "user.email")) {
+               strncpy(git_default_email, value, sizeof(git_default_email));
+               return 0;
+       }
+
+       if (!strcmp(var, "i18n.commitencoding")) {
+               strncpy(git_commit_encoding, value, sizeof(git_commit_encoding));
+               return 0;
+       }
+
+       /* Add other config variables here and to Documentation/config.txt. */
+       return 0;
+}
+
+int git_config_from_file(config_fn_t fn, const char *filename)
+{
+       int ret;
+       FILE *f = fopen(filename, "r");
+
+       ret = -1;
+       if (f) {
+               config_file = f;
+               config_file_name = filename;
+               config_linenr = 1;
+               ret = git_parse_file(fn);
+               fclose(f);
+               config_file_name = NULL;
+       }
+       return ret;
+}
+
+int git_config(config_fn_t fn)
+{
+       return git_config_from_file(fn, git_path("config"));
+}
+
+/*
+ * Find all the stuff for git_config_set() below.
+ */
+
+#define MAX_MATCHES 512
+
+static struct {
+       int baselen;
+       char* key;
+       int do_not_match;
+       regex_t* value_regex;
+       int multi_replace;
+       off_t offset[MAX_MATCHES];
+       enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
+       int seen;
+} store;
+
+static int matches(const char* key, const char* value)
+{
+       return !strcmp(key, store.key) &&
+               (store.value_regex == NULL ||
+                (store.do_not_match ^
+                 !regexec(store.value_regex, value, 0, NULL, 0)));
+}
+
+static int store_aux(const char* key, const char* value)
+{
+       switch (store.state) {
+       case KEY_SEEN:
+               if (matches(key, value)) {
+                       if (store.seen == 1 && store.multi_replace == 0) {
+                               fprintf(stderr,
+                                       "Warning: %s has multiple values\n",
+                                       key);
+                       } else if (store.seen >= MAX_MATCHES) {
+                               fprintf(stderr, "Too many matches\n");
+                               return 1;
+                       }
+
+                       store.offset[store.seen] = ftell(config_file);
+                       store.seen++;
+               }
+               break;
+       case SECTION_SEEN:
+               if (strncmp(key, store.key, store.baselen+1)) {
+                       store.state = SECTION_END_SEEN;
+                       break;
+               } else
+                       /* do not increment matches: this is no match */
+                       store.offset[store.seen] = ftell(config_file);
+               /* fallthru */
+       case SECTION_END_SEEN:
+       case START:
+               if (matches(key, value)) {
+                       store.offset[store.seen] = ftell(config_file);
+                       store.state = KEY_SEEN;
+                       store.seen++;
+               } else {
+                       if (strrchr(key, '.') - key == store.baselen &&
+                             !strncmp(key, store.key, store.baselen)) {
+                                       store.state = SECTION_SEEN;
+                                       store.offset[store.seen] = ftell(config_file);
+                       }
+               }
+       }
+       return 0;
+}
+
+static void store_write_section(int fd, const char* key)
+{
+       const char *dot = strchr(key, '.');
+       int len1 = store.baselen, len2 = -1;
+
+       dot = strchr(key, '.');
+       if (dot) {
+               int dotlen = dot - key;
+               if (dotlen < len1) {
+                       len2 = len1 - dotlen - 1;
+                       len1 = dotlen;
+               }
+       }
+
+       write(fd, "[", 1);
+       write(fd, key, len1);
+       if (len2 >= 0) {
+               write(fd, " \"", 2);
+               while (--len2 >= 0) {
+                       unsigned char c = *++dot;
+                       if (c == '"')
+                               write(fd, "\\", 1);
+                       write(fd, &c, 1);
+               }
+               write(fd, "\"", 1);
+       }
+       write(fd, "]\n", 2);
+}
+
+static void store_write_pair(int fd, const char* key, const char* value)
+{
+       int i;
+
+       write(fd, "\t", 1);
+       write(fd, key+store.baselen+1,
+               strlen(key+store.baselen+1));
+       write(fd, " = ", 3);
+       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);
+}
+
+static int find_beginning_of_line(const char* contents, int size,
+       int offset_, int* found_bracket)
+{
+       int equal_offset = size, bracket_offset = size;
+       int offset;
+
+       for (offset = offset_-2; offset > 0 
+                       && contents[offset] != '\n'; offset--)
+               switch (contents[offset]) {
+                       case '=': equal_offset = offset; break;
+                       case ']': bracket_offset = offset; break;
+               }
+       if (bracket_offset < equal_offset) {
+               *found_bracket = 1;
+               offset = bracket_offset+1;
+       } else
+               offset++;
+
+       return offset;
+}
+
+int git_config_set(const char* key, const char* value)
+{
+       return git_config_set_multivar(key, value, NULL, 0);
+}
+
+/*
+ * If value==NULL, unset in (remove from) config,
+ * if value_regex!=NULL, disregard key/value pairs where value does not match.
+ * if multi_replace==0, nothing, or only one matching key/value is replaced,
+ *     else all matching key/values (regardless how many) are removed,
+ *     before the new pair is written.
+ *
+ * Returns 0 on success.
+ *
+ * This function does this:
+ *
+ * - it locks the config file by creating ".git/config.lock"
+ *
+ * - it then parses the config using store_aux() as validator to find
+ *   the position on the key/value pair to replace. If it is to be unset,
+ *   it must be found exactly once.
+ *
+ * - the config file is mmap()ed and the part before the match (if any) is
+ *   written to the lock file, then the changed part and the rest.
+ *
+ * - the config file is removed and the lock file rename()d to it.
+ *
+ */
+int git_config_set_multivar(const char* key, const char* value,
+       const char* value_regex, int multi_replace)
+{
+       int i, dot;
+       int fd = -1, in_fd;
+       int ret;
+       char* config_filename = strdup(git_path("config"));
+       char* lock_file = strdup(git_path("config.lock"));
+       const char* last_dot = strrchr(key, '.');
+
+       /*
+        * Since "key" actually contains the section name and the real
+        * key name separated by a dot, we have to know where the dot is.
+        */
+
+       if (last_dot == NULL) {
+               fprintf(stderr, "key does not contain a section: %s\n", key);
+               ret = 2;
+               goto out_free;
+       }
+       store.baselen = last_dot - key;
+
+       store.multi_replace = multi_replace;
+
+       /*
+        * Validate the key and while at it, lower case it for matching.
+        */
+       store.key = (char*)malloc(strlen(key)+1);
+       dot = 0;
+       for (i = 0; key[i]; i++) {
+               unsigned char c = key[i];
+               if (c == '.')
+                       dot = 1;
+               /* Leave the extended basename untouched.. */
+               if (!dot || i > store.baselen) {
+                       if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                               fprintf(stderr, "invalid key: %s\n", key);
+                               free(store.key);
+                               ret = 1;
+                               goto out_free;
+                       }
+                       c = tolower(c);
+               }
+               store.key[i] = c;
+       }
+       store.key[i] = 0;
+
+       /*
+        * The lock_file serves a purpose in addition to locking: the new
+        * contents of .git/config will be written into it.
+        */
+       fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0) {
+               fprintf(stderr, "could not lock config file\n");
+               free(store.key);
+               ret = -1;
+               goto out_free;
+       }
+
+       /*
+        * If .git/config does not exist yet, write a minimal version.
+        */
+       in_fd = open(config_filename, O_RDONLY);
+       if ( in_fd < 0 ) {
+               free(store.key);
+
+               if ( ENOENT != errno ) {
+                       error("opening %s: %s", config_filename,
+                             strerror(errno));
+                       ret = 3; /* same as "invalid config file" */
+                       goto out_free;
+               }
+               /* if nothing to unset, error out */
+               if (value == NULL) {
+                       ret = 5;
+                       goto out_free;
+               }
+
+               store.key = (char*)key;
+               store_write_section(fd, key);
+               store_write_pair(fd, key, value);
+       } else{
+               struct stat st;
+               char* contents;
+               int i, copy_begin, copy_end, new_line = 0;
+
+               if (value_regex == NULL)
+                       store.value_regex = NULL;
+               else {
+                       if (value_regex[0] == '!') {
+                               store.do_not_match = 1;
+                               value_regex++;
+                       } else
+                               store.do_not_match = 0;
+
+                       store.value_regex = (regex_t*)malloc(sizeof(regex_t));
+                       if (regcomp(store.value_regex, value_regex,
+                                       REG_EXTENDED)) {
+                               fprintf(stderr, "Invalid pattern: %s\n",
+                                       value_regex);
+                               free(store.value_regex);
+                               ret = 6;
+                               goto out_free;
+                       }
+               }
+
+               store.offset[0] = 0;
+               store.state = START;
+               store.seen = 0;
+
+               /*
+                * After this, store.offset will contain the *end* offset
+                * of the last match, or remain at 0 if no match was found.
+                * As a side effect, we make sure to transform only a valid
+                * existing config file.
+                */
+               if (git_config(store_aux)) {
+                       fprintf(stderr, "invalid config file\n");
+                       free(store.key);
+                       if (store.value_regex != NULL) {
+                               regfree(store.value_regex);
+                               free(store.value_regex);
+                       }
+                       ret = 3;
+                       goto out_free;
+               }
+
+               free(store.key);
+               if (store.value_regex != NULL) {
+                       regfree(store.value_regex);
+                       free(store.value_regex);
+               }
+
+               /* if nothing to unset, or too many matches, error out */
+               if ((store.seen == 0 && value == NULL) ||
+                               (store.seen > 1 && multi_replace == 0)) {
+                       ret = 5;
+                       goto out_free;
+               }
+
+               fstat(in_fd, &st);
+               contents = mmap(NULL, st.st_size, PROT_READ,
+                       MAP_PRIVATE, in_fd, 0);
+               close(in_fd);
+
+               if (store.seen == 0)
+                       store.seen = 1;
+
+               for (i = 0, copy_begin = 0; i < store.seen; i++) {
+                       if (store.offset[i] == 0) {
+                               store.offset[i] = copy_end = st.st_size;
+                       } else if (store.state != KEY_SEEN) {
+                               copy_end = store.offset[i];
+                       } else
+                               copy_end = find_beginning_of_line(
+                                       contents, st.st_size,
+                                       store.offset[i]-2, &new_line);
+
+                       /* 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);
+                       }
+                       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);
+               }
+
+               /* write the rest of the config */
+               if (copy_begin < st.st_size)
+                       write(fd, contents + copy_begin,
+                               st.st_size - copy_begin);
+
+               munmap(contents, st.st_size);
+               unlink(config_filename);
+       }
+
+       if (rename(lock_file, config_filename) < 0) {
+               fprintf(stderr, "Could not rename the lock file?\n");
+               ret = 4;
+               goto out_free;
+       }
+
+       ret = 0;
+
+out_free:
+       if (0 <= fd)
+               close(fd);
+       if (config_filename)
+               free(config_filename);
+       if (lock_file) {
+               unlink(lock_file);
+               free(lock_file);
+       }
+       return ret;
+}
+
+
diff --git a/connect.c b/connect.c
new file mode 100644 (file)
index 0000000..eca94f7
--- /dev/null
+++ b/connect.c
@@ -0,0 +1,711 @@
+#include "git-compat-util.h"
+#include "cache.h"
+#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>
+
+static char *server_capabilities = NULL;
+
+/*
+ * Read all the refs from the other end
+ */
+struct ref **get_remote_heads(int in, struct ref **list,
+                             int nr_match, char **match, int ignore_funny)
+{
+       *list = NULL;
+       for (;;) {
+               struct ref *ref;
+               unsigned char old_sha1[20];
+               static char buffer[1000];
+               char *name;
+               int len, name_len;
+
+               len = packet_read_line(in, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (buffer[len-1] == '\n')
+                       buffer[--len] = 0;
+
+               if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
+                       die("protocol error: expected sha/ref, got '%s'", buffer);
+               name = buffer + 41;
+
+               name_len = strlen(name);
+               if (len != name_len + 41) {
+                       if (server_capabilities)
+                               free(server_capabilities);
+                       server_capabilities = strdup(name + name_len + 1);
+               }
+
+               if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
+                   check_ref_format(name + 5))
+                       continue;
+
+               if (nr_match && !path_match(name, nr_match, match))
+                       continue;
+               ref = xcalloc(1, sizeof(*ref) + len - 40);
+               memcpy(ref->old_sha1, old_sha1, 20);
+               memcpy(ref->name, buffer + 41, len - 40);
+               *list = ref;
+               list = &ref->next;
+       }
+       return list;
+}
+
+int server_supports(const char *feature)
+{
+       return server_capabilities &&
+               strstr(server_capabilities, feature) != NULL;
+}
+
+int get_ack(int fd, unsigned char *result_sha1)
+{
+       static char line[1000];
+       int len = packet_read_line(fd, line, sizeof(line));
+
+       if (!len)
+               die("git-fetch-pack: expected ACK/NAK, got EOF");
+       if (line[len-1] == '\n')
+               line[--len] = 0;
+       if (!strcmp(line, "NAK"))
+               return 0;
+       if (!strncmp(line, "ACK ", 4)) {
+               if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (strstr(line+45, "continue"))
+                               return 2;
+                       return 1;
+               }
+       }
+       die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+int path_match(const char *path, int nr, char **match)
+{
+       int i;
+       int pathlen = strlen(path);
+
+       for (i = 0; i < nr; i++) {
+               char *s = match[i];
+               int len = strlen(s);
+
+               if (!len || len > pathlen)
+                       continue;
+               if (memcmp(path + pathlen - len, s, len))
+                       continue;
+               if (pathlen > len && path[pathlen - len - 1] != '/')
+                       continue;
+               *s = 0;
+               return (i + 1);
+       }
+       return 0;
+}
+
+struct refspec {
+       char *src;
+       char *dst;
+       char force;
+};
+
+/*
+ * A:B means fast forward remote B with local A.
+ * +A:B means overwrite remote B with local A.
+ * +A is a shorthand for +A:A.
+ * A is a shorthand for A:A.
+ */
+static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
+{
+       int i;
+       struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
+       for (i = 0; i < nr_refspec; i++) {
+               char *sp, *dp, *ep;
+               sp = refspec[i];
+               if (*sp == '+') {
+                       rs[i].force = 1;
+                       sp++;
+               }
+               ep = strchr(sp, ':');
+               if (ep) {
+                       dp = ep + 1;
+                       *ep = 0;
+               }
+               else
+                       dp = sp;
+               rs[i].src = sp;
+               rs[i].dst = dp;
+       }
+       rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
+       return rs;
+}
+
+static int count_refspec_match(const char *pattern,
+                              struct ref *refs,
+                              struct ref **matched_ref)
+{
+       int match;
+       int patlen = strlen(pattern);
+
+       for (match = 0; refs; refs = refs->next) {
+               char *name = refs->name;
+               int namelen = strlen(name);
+               if (namelen < patlen ||
+                   memcmp(name + namelen - patlen, pattern, patlen))
+                       continue;
+               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+                       continue;
+               match++;
+               *matched_ref = refs;
+       }
+       return match;
+}
+
+static void link_dst_tail(struct ref *ref, struct ref ***tail)
+{
+       **tail = ref;
+       *tail = &ref->next;
+       **tail = NULL;
+}
+
+static struct ref *try_explicit_object_name(const char *name)
+{
+       unsigned char sha1[20];
+       struct ref *ref;
+       int len;
+       if (get_sha1(name, sha1))
+               return NULL;
+       len = strlen(name) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->name, name, len);
+       memcpy(ref->new_sha1, sha1, 20);
+       return ref;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+                              struct ref ***dst_tail, struct refspec *rs)
+{
+       int i, errs;
+       for (i = errs = 0; rs[i].src; i++) {
+               struct ref *matched_src, *matched_dst;
+
+               matched_src = matched_dst = NULL;
+               switch (count_refspec_match(rs[i].src, src, &matched_src)) {
+               case 1:
+                       break;
+               case 0:
+                       /* The source could be in the get_sha1() format
+                        * not a reference name.
+                        */
+                       matched_src = try_explicit_object_name(rs[i].src);
+                       if (matched_src)
+                               break;
+                       errs = 1;
+                       error("src refspec %s does not match any.",
+                             rs[i].src);
+                       break;
+               default:
+                       errs = 1;
+                       error("src refspec %s matches more than one.",
+                             rs[i].src);
+                       break;
+               }
+               switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
+               case 1:
+                       break;
+               case 0:
+                       if (!memcmp(rs[i].dst, "refs/", 5)) {
+                               int len = strlen(rs[i].dst) + 1;
+                               matched_dst = xcalloc(1, sizeof(*dst) + len);
+                               memcpy(matched_dst->name, rs[i].dst, len);
+                               link_dst_tail(matched_dst, dst_tail);
+                       }
+                       else if (!strcmp(rs[i].src, rs[i].dst) &&
+                                matched_src) {
+                               /* pushing "master:master" when
+                                * remote does not have master yet.
+                                */
+                               int len = strlen(matched_src->name) + 1;
+                               matched_dst = xcalloc(1, sizeof(*dst) + len);
+                               memcpy(matched_dst->name, matched_src->name,
+                                      len);
+                               link_dst_tail(matched_dst, dst_tail);
+                       }
+                       else {
+                               errs = 1;
+                               error("dst refspec %s does not match any "
+                                     "existing ref on the remote and does "
+                                     "not start with refs/.", rs[i].dst);
+                       }
+                       break;
+               default:
+                       errs = 1;
+                       error("dst refspec %s matches more than one.",
+                             rs[i].dst);
+                       break;
+               }
+               if (errs)
+                       continue;
+               if (matched_dst->peer_ref) {
+                       errs = 1;
+                       error("dst ref %s receives from more than one src.",
+                             matched_dst->name);
+               }
+               else {
+                       matched_dst->peer_ref = matched_src;
+                       matched_dst->force = rs[i].force;
+               }
+       }
+       return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+       for ( ; list; list = list->next)
+               if (!strcmp(list->name, name))
+                       return list;
+       return NULL;
+}
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+              int nr_refspec, char **refspec, int all)
+{
+       struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
+
+       if (nr_refspec)
+               return match_explicit_refs(src, dst, dst_tail, rs);
+
+       /* pick the remainder */
+       for ( ; src; src = src->next) {
+               struct ref *dst_peer;
+               if (src->peer_ref)
+                       continue;
+               dst_peer = find_ref_by_name(dst, src->name);
+               if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
+                       continue;
+               if (!dst_peer) {
+                       /* Create a new one and link it */
+                       int len = strlen(src->name) + 1;
+                       dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
+                       memcpy(dst_peer->name, src->name, len);
+                       memcpy(dst_peer->new_sha1, src->new_sha1, 20);
+                       link_dst_tail(dst_peer, dst_tail);
+               }
+               dst_peer->peer_ref = src;
+       }
+       return 0;
+}
+
+enum protocol {
+       PROTO_LOCAL = 1,
+       PROTO_SSH,
+       PROTO_GIT,
+};
+
+static enum protocol get_protocol(const char *name)
+{
+       if (!strcmp(name, "ssh"))
+               return PROTO_SSH;
+       if (!strcmp(name, "git"))
+               return PROTO_GIT;
+       if (!strcmp(name, "git+ssh"))
+               return PROTO_SSH;
+       if (!strcmp(name, "ssh+git"))
+               return PROTO_SSH;
+       die("I don't handle protocol '%s'", name);
+}
+
+#define STR_(s)        # s
+#define STR(s) STR_(s)
+
+#ifndef NO_IPV6
+
+/*
+ * Returns a connected socket() fd, or else die()s.
+ */
+static int git_tcp_connect_sock(char *host)
+{
+       int sockfd = -1;
+       char *colon, *end;
+       char *port = STR(DEFAULT_GIT_PORT);
+       struct addrinfo hints, *ai0, *ai;
+       int gai;
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               port = colon + 1;
+       }
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+
+       gai = getaddrinfo(host, port, &hints, &ai);
+       if (gai)
+               die("Unable to look up %s (%s)", host, gai_strerror(gai));
+
+       for (ai0 = ai; ai; ai = ai->ai_next) {
+               sockfd = socket(ai->ai_family,
+                               ai->ai_socktype, ai->ai_protocol);
+               if (sockfd < 0)
+                       continue;
+               if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       close(sockfd);
+                       sockfd = -1;
+                       continue;
+               }
+               break;
+       }
+
+       freeaddrinfo(ai0);
+
+       if (sockfd < 0)
+               die("unable to connect a socket (%s)", strerror(errno));
+
+       return sockfd;
+}
+
+#else /* NO_IPV6 */
+
+/*
+ * Returns a connected socket() fd, or else die()s.
+ */
+static int git_tcp_connect_sock(char *host)
+{
+       int sockfd = -1;
+       char *colon, *end;
+       char *port = STR(DEFAULT_GIT_PORT), *ep;
+       struct hostent *he;
+       struct sockaddr_in sa;
+       char **ap;
+       unsigned int nport;
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               port = colon + 1;
+       }
+
+       he = gethostbyname(host);
+       if (!he)
+               die("Unable to look up %s (%s)", host, hstrerror(h_errno));
+       nport = strtoul(port, &ep, 10);
+       if ( ep == port || *ep ) {
+               /* Not numeric */
+               struct servent *se = getservbyname(port,"tcp");
+               if ( !se )
+                       die("Unknown port %s\n", port);
+               nport = se->s_port;
+       }
+
+       for (ap = he->h_addr_list; *ap; ap++) {
+               sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+               if (sockfd < 0)
+                       continue;
+
+               memset(&sa, 0, sizeof sa);
+               sa.sin_family = he->h_addrtype;
+               sa.sin_port = htons(nport);
+               memcpy(&sa.sin_addr, *ap, he->h_length);
+
+               if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+                       close(sockfd);
+                       sockfd = -1;
+                       continue;
+               }
+               break;
+       }
+
+       if (sockfd < 0)
+               die("unable to connect a socket (%s)", strerror(errno));
+
+       return sockfd;
+}
+
+#endif /* NO_IPV6 */
+
+
+static void git_tcp_connect(int fd[2],
+                           const char *prog, char *host, char *path)
+{
+       int sockfd = git_tcp_connect_sock(host);
+
+       fd[0] = sockfd;
+       fd[1] = sockfd;
+}
+
+
+static char *git_proxy_command = NULL;
+static const char *rhost_name = NULL;
+static int rhost_len;
+
+static int git_proxy_command_options(const char *var, const char *value)
+{
+       if (!strcmp(var, "core.gitproxy")) {
+               const char *for_pos;
+               int matchlen = -1;
+               int hostlen;
+
+               if (git_proxy_command)
+                       return 0;
+               /* [core]
+                * ;# matches www.kernel.org as well
+                * gitproxy = netcatter-1 for kernel.org
+                * gitproxy = netcatter-2 for sample.xz
+                * gitproxy = netcatter-default
+                */
+               for_pos = strstr(value, " for ");
+               if (!for_pos)
+                       /* matches everybody */
+                       matchlen = strlen(value);
+               else {
+                       hostlen = strlen(for_pos + 5);
+                       if (rhost_len < hostlen)
+                               matchlen = -1;
+                       else if (!strncmp(for_pos + 5,
+                                         rhost_name + rhost_len - hostlen,
+                                         hostlen) &&
+                                ((rhost_len == hostlen) ||
+                                 rhost_name[rhost_len - hostlen -1] == '.'))
+                               matchlen = for_pos - value;
+                       else
+                               matchlen = -1;
+               }
+               if (0 <= matchlen) {
+                       /* core.gitproxy = none for kernel.org */
+                       if (matchlen == 4 && 
+                           !memcmp(value, "none", 4))
+                               matchlen = 0;
+                       git_proxy_command = xmalloc(matchlen + 1);
+                       memcpy(git_proxy_command, value, matchlen);
+                       git_proxy_command[matchlen] = 0;
+               }
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+static int git_use_proxy(const char *host)
+{
+       rhost_name = host;
+       rhost_len = strlen(host);
+       git_proxy_command = getenv("GIT_PROXY_COMMAND");
+       git_config(git_proxy_command_options);
+       rhost_name = NULL;
+       return (git_proxy_command && *git_proxy_command);
+}
+
+static void git_proxy_connect(int fd[2],
+                             const char *prog, char *host, char *path)
+{
+       char *port = STR(DEFAULT_GIT_PORT);
+       char *colon, *end;
+       int pipefd[2][2];
+       pid_t pid;
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+       colon = strchr(end, ':');
+
+       if (colon) {
+               *colon = 0;
+               port = colon + 1;
+       }
+
+       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+               die("unable to create pipe pair for communication");
+       pid = fork();
+       if (!pid) {
+               dup2(pipefd[1][0], 0);
+               dup2(pipefd[0][1], 1);
+               close(pipefd[0][0]);
+               close(pipefd[0][1]);
+               close(pipefd[1][0]);
+               close(pipefd[1][1]);
+               execlp(git_proxy_command, git_proxy_command, host, port, NULL);
+               die("exec failed");
+       }
+       if (pid < 0)
+               die("fork failed");
+       fd[0] = pipefd[0][0];
+       fd[1] = pipefd[1][1];
+       close(pipefd[0][1]);
+       close(pipefd[1][0]);
+}
+
+/*
+ * Yeah, yeah, fixme. Need to pass in the heads etc.
+ */
+int git_connect(int fd[2], char *url, const char *prog)
+{
+       char command[1024];
+       char *host, *path = url;
+       char *end;
+       int c;
+       int pipefd[2][2];
+       pid_t pid;
+       enum protocol protocol = PROTO_LOCAL;
+       int free_path = 0;
+
+       host = strstr(url, "://");
+       if(host) {
+               *host = '\0';
+               protocol = get_protocol(url);
+               host += 3;
+               c = '/';
+       } else {
+               host = url;
+               c = ':';
+       }
+
+       if (host[0] == '[') {
+               end = strchr(host + 1, ']');
+               if (end) {
+                       *end = 0;
+                       end++;
+                       host++;
+               } else
+                       end = host;
+       } else
+               end = host;
+
+       path = strchr(end, c);
+       if (c == ':') {
+               if (path) {
+                       protocol = PROTO_SSH;
+                       *path++ = '\0';
+               } else
+                       path = host;
+       }
+
+       if (!path || !*path)
+               die("No path specified. See 'man git-pull' for valid url syntax");
+
+       /*
+        * null-terminate hostname and point path to ~ for URL's like this:
+        *    ssh://host.xz/~user/repo
+        */
+       if (protocol != PROTO_LOCAL && host != url) {
+               char *ptr = path;
+               if (path[1] == '~')
+                       path++;
+               else {
+                       path = strdup(ptr);
+                       free_path = 1;
+               }
+
+               *ptr = '\0';
+       }
+
+       if (protocol == PROTO_GIT) {
+               /* These underlying connection commands die() if they
+                * cannot connect.
+                */
+               char *target_host = strdup(host);
+               if (git_use_proxy(host))
+                       git_proxy_connect(fd, prog, host, path);
+               else
+                       git_tcp_connect(fd, prog, host, path);
+               /*
+                * Separate original protocol components prog and path
+                * from extended components with a NUL byte.
+                */
+               packet_write(fd[1],
+                            "%s %s%chost=%s%c",
+                            prog, path, 0,
+                            target_host, 0);
+               free(target_host);
+               if (free_path)
+                       free(path);
+               return 0;
+       }
+
+       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+               die("unable to create pipe pair for communication");
+       pid = fork();
+       if (!pid) {
+               snprintf(command, sizeof(command), "%s %s", prog,
+                        sq_quote(path));
+               dup2(pipefd[1][0], 0);
+               dup2(pipefd[0][1], 1);
+               close(pipefd[0][0]);
+               close(pipefd[0][1]);
+               close(pipefd[1][0]);
+               close(pipefd[1][1]);
+               if (protocol == PROTO_SSH) {
+                       const char *ssh, *ssh_basename;
+                       ssh = getenv("GIT_SSH");
+                       if (!ssh) ssh = "ssh";
+                       ssh_basename = strrchr(ssh, '/');
+                       if (!ssh_basename)
+                               ssh_basename = ssh;
+                       else
+                               ssh_basename++;
+                       execlp(ssh, ssh_basename, host, command, NULL);
+               }
+               else {
+                       unsetenv(ALTERNATE_DB_ENVIRONMENT);
+                       unsetenv(DB_ENVIRONMENT);
+                       unsetenv(GIT_DIR_ENVIRONMENT);
+                       unsetenv(GRAFT_ENVIRONMENT);
+                       unsetenv(INDEX_ENVIRONMENT);
+                       execlp("sh", "sh", "-c", command, NULL);
+               }
+               die("exec failed");
+       }
+       fd[0] = pipefd[0][0];
+       fd[1] = pipefd[1][1];
+       close(pipefd[0][1]);
+       close(pipefd[1][0]);
+       if (free_path)
+               free(path);
+       return pid;
+}
+
+int finish_connect(pid_t pid)
+{
+       int ret;
+
+       for (;;) {
+               ret = waitpid(pid, NULL, 0);
+               if (!ret)
+                       break;
+               if (errno != EINTR)
+                       break;
+       }
+       return ret;
+}
diff --git a/contrib/README b/contrib/README
new file mode 100644 (file)
index 0000000..e1c0a01
--- /dev/null
@@ -0,0 +1,44 @@
+Contributed Software
+
+Although these pieces are available as part of the official git
+source tree, they are in somewhat different status.  The
+intention is to keep interesting tools around git here, maybe
+even experimental ones, to give users an easier access to them,
+and to give tools wider exposure, so that they can be improved
+faster.
+
+I am not expecting to touch these myself that much.  As far as
+my day-to-day operation is concerned, these subdirectories are
+owned by their respective primary authors.  I am willing to help
+if users of these components and the contrib/ subtree "owners"
+have technical/design issues to resolve, but the initiative to
+fix and/or enhance things _must_ be on the side of the subtree
+owners.  IOW, I won't be actively looking for bugs and rooms for
+enhancements in them as the git maintainer -- I may only do so
+just as one of the users when I want to scratch my own itch.  If
+you have patches to things in contrib/ area, the patch should be
+first sent to the primary author, and then the primary author
+should ack and forward it to me (git pull request is nicer).
+This is the same way as how I have been treating gitk, and to a
+lesser degree various foreign SCM interfaces, so you know the
+drill.
+
+I expect that things that start their life in the contrib/ area
+to graduate out of contrib/ once they mature, either by becoming
+projects on their own, or moving to the toplevel directory.  On
+the other hand, I expect I'll be proposing removal of disused
+and inactive ones from time to time.
+
+If you have new things to add to this area, please first propose
+it on the git mailing list, and after a list discussion proves
+there are some general interests (it does not have to be a
+list-wide consensus for a tool targeted to a relatively narrow
+audience -- for example I do not work with projects whose
+upstream is svn, so I have no use for git-svn myself, but it is
+of general interest for people who need to interoperate with SVN
+repositories in a way git-svn works better than git-svnimport),
+submit a patch to create a subdirectory of contrib/ and put your
+stuff there.
+
+-jc
+
diff --git a/contrib/colordiff/README b/contrib/colordiff/README
new file mode 100644 (file)
index 0000000..2678fdf
--- /dev/null
@@ -0,0 +1,2 @@
+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
new file mode 100755 (executable)
index 0000000..5789cfb
--- /dev/null
@@ -0,0 +1,196 @@
+#!/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 specfic 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}";
+}
diff --git a/contrib/emacs/.gitignore b/contrib/emacs/.gitignore
new file mode 100644 (file)
index 0000000..c531d98
--- /dev/null
@@ -0,0 +1 @@
+*.elc
diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile
new file mode 100644 (file)
index 0000000..d3619db
--- /dev/null
@@ -0,0 +1,20 @@
+## Build and install stuff
+
+EMACS = emacs
+
+ELC = git.elc vc-git.elc
+INSTALL = install
+INSTALL_ELC = $(INSTALL) -m 644
+prefix = $(HOME)
+emacsdir = $(prefix)/share/emacs/site-lisp
+
+all: $(ELC)
+
+install: all
+       $(INSTALL) -d $(emacsdir)
+       $(INSTALL_ELC) $(ELC) $(emacsdir)
+
+%.elc: %.el
+       $(EMACS) --batch --eval '(byte-compile-file "$<")'
+
+clean:; rm -f $(ELC)
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
new file mode 100644 (file)
index 0000000..ebd00ef
--- /dev/null
@@ -0,0 +1,1012 @@
+;;; git.el --- A user interface for git
+
+;; Copyright (C) 2005, 2006 Alexandre Julliard <julliard@winehq.org>
+
+;; Version: 1.0
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains an interface for the git version control
+;; system. It provides easy access to the most frequently used git
+;; commands. The user interface is as far as possible identical to
+;; that of the PCL-CVS mode.
+;;
+;; To install: put this file on the load-path and place the following
+;; in your .emacs file:
+;;
+;;    (require 'git)
+;;
+;; To start: `M-x git-status'
+;;
+;; TODO
+;;  - portability to XEmacs
+;;  - better handling of subprocess errors
+;;  - hook into file save (after-save-hook)
+;;  - diff against other branch
+;;  - renaming files from the status buffer
+;;  - creating tags
+;;  - fetch/pull
+;;  - switching branches
+;;  - revlist browser
+;;  - git-show-branch browser
+;;  - menus
+;;
+
+(eval-when-compile (require 'cl))
+(require 'ewoc)
+
+
+;;;; Customizations
+;;;; ------------------------------------------------------------
+
+(defgroup git nil
+  "Git user interface")
+
+(defcustom git-committer-name nil
+  "User name to use for commits.
+The default is to fall back to the repository config, then to `add-log-full-name' and then to `user-full-name'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Name")))
+
+(defcustom git-committer-email nil
+  "Email address to use for commits.
+The default is to fall back to the git repository config, then to `add-log-mailing-address' and then to `user-mail-address'."
+  :group 'git
+  :type '(choice (const :tag "Default" nil)
+                 (string :tag "Email")))
+
+(defcustom git-commits-coding-system 'utf-8
+  "Default coding system for the log message of git commits."
+  :group 'git
+  :type 'coding-system)
+
+(defcustom git-append-signed-off-by nil
+  "Whether to append a Signed-off-by line to the commit message before editing."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-per-dir-ignore-file ".gitignore"
+  "Name of the per-directory ignore file."
+  :group 'git
+  :type 'string)
+
+(defface git-status-face
+  '((((class color) (background light)) (:foreground "purple")))
+  "Git mode face used to highlight added and modified files."
+  :group 'git)
+
+(defface git-unmerged-face
+  '((((class color) (background light)) (:foreground "red" :bold t)))
+  "Git mode face used to highlight unmerged files."
+  :group 'git)
+
+(defface git-unknown-face
+  '((((class color) (background light)) (:foreground "goldenrod" :bold t)))
+  "Git mode face used to highlight unknown files."
+  :group 'git)
+
+(defface git-uptodate-face
+  '((((class color) (background light)) (:foreground "grey60")))
+  "Git mode face used to highlight up-to-date files."
+  :group 'git)
+
+(defface git-ignored-face
+  '((((class color) (background light)) (:foreground "grey60")))
+  "Git mode face used to highlight ignored files."
+  :group 'git)
+
+(defface git-mark-face
+  '((((class color) (background light)) (:foreground "red" :bold t)))
+  "Git mode face used for the file marks."
+  :group 'git)
+
+(defface git-header-face
+  '((((class color) (background light)) (:foreground "blue")))
+  "Git mode face used for commit headers."
+  :group 'git)
+
+(defface git-separator-face
+  '((((class color) (background light)) (:foreground "brown")))
+  "Git mode face used for commit separator."
+  :group 'git)
+
+(defface git-permission-face
+  '((((class color) (background light)) (:foreground "green" :bold t)))
+  "Git mode face used for permission changes."
+  :group 'git)
+
+
+;;;; Utilities
+;;;; ------------------------------------------------------------
+
+(defconst git-log-msg-separator "--- log message follows this line ---")
+
+(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))
+
+(defun git-call-process-env (buffer env &rest args)
+  "Wrapper for call-process that sets environment strings."
+  (if env
+      (apply #'call-process "env" nil buffer nil
+             (append (git-get-env-strings env) (list "git") args))
+    (apply #'call-process "git" nil buffer nil args)))
+
+(defun git-call-process-env-string (env &rest args)
+  "Wrapper for call-process that sets environment strings, and returns the process output as a string."
+  (with-temp-buffer
+    (and (eq 0 (apply #' git-call-process-env t env args))
+         (buffer-string))))
+
+(defun git-run-process-region (buffer start end program args)
+  "Run a git process with a buffer region as input."
+  (let ((output-buffer (current-buffer))
+        (dir default-directory))
+    (with-current-buffer buffer
+      (cd dir)
+      (apply #'call-process-region start end program
+             nil (list output-buffer nil) nil args))))
+
+(defun git-run-command-buffer (buffer-name &rest args)
+  "Run a git command, sending the output to a buffer named BUFFER-NAME."
+  (let ((dir default-directory)
+        (buffer (get-buffer-create buffer-name)))
+    (message "Running git %s..." (car args))
+    (with-current-buffer buffer
+      (let ((default-directory dir)
+            (buffer-read-only nil))
+        (erase-buffer)
+        (apply #'git-call-process-env buffer nil args)))
+    (message "Running git %s...done" (car args))
+    buffer))
+
+(defun git-run-command (buffer env &rest args)
+  (message "Running git %s..." (car args))
+  (apply #'git-call-process-env buffer env args)
+  (message "Running git %s...done" (car args)))
+
+(defun git-run-command-region (buffer start end env &rest args)
+  "Run a git command with specified buffer region as input."
+  (message "Running git %s..." (car args))
+  (unless (eq 0 (if env
+                    (git-run-process-region
+                     buffer start end "env"
+                     (append (git-get-env-strings env) (list "git") args))
+                  (git-run-process-region
+                   buffer start end "git" args)))
+    (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))
+  (message "Running git %s...done" (car args)))
+
+(defun git-get-string-sha1 (string)
+  "Read a SHA1 from the specified string."
+  (and string
+       (string-match "[0-9a-f]\\{40\\}" string)
+       (match-string 0 string)))
+
+(defun git-get-committer-name ()
+  "Return the name to use as GIT_COMMITTER_NAME."
+  ; copied from log-edit
+  (or git-committer-name
+      (git-repo-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)))
+
+(defun git-get-committer-email ()
+  "Return the email address to use as GIT_COMMITTER_EMAIL."
+  ; copied from log-edit
+  (or git-committer-email
+      (git-repo-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)))
+
+(defun git-escape-file-name (name)
+  "Escape a file name if necessary."
+  (if (string-match "[\n\t\"\\]" name)
+      (concat "\""
+              (mapconcat (lambda (c)
+                   (case c
+                     (?\n "\\n")
+                     (?\t "\\t")
+                     (?\\ "\\\\")
+                     (?\" "\\\"")
+                     (t (char-to-string c))))
+                 name "")
+              "\"")
+    name))
+
+(defun git-get-top-dir (dir)
+  "Retrieve the top-level directory of a git tree."
+  (let ((cdup (with-output-to-string
+                (with-current-buffer standard-output
+                  (cd dir)
+                  (unless (eq 0 (call-process "git" nil t nil "rev-parse" "--show-cdup"))
+                    (error "cannot find top-level git tree for %s." dir))))))
+    (expand-file-name (concat (file-name-as-directory dir)
+                              (car (split-string cdup "\n"))))))
+
+;stolen from pcl-cvs
+(defun git-append-to-ignore (file)
+  "Add a file name to the ignore file in its directory."
+  (let* ((fullname (expand-file-name file))
+         (dir (file-name-directory fullname))
+         (name (file-name-nondirectory fullname))
+         (ignore-name (expand-file-name git-per-dir-ignore-file dir))
+         (created (not (file-exists-p ignore-name))))
+  (save-window-excursion
+    (set-buffer (find-file-noselect ignore-name))
+    (goto-char (point-max))
+    (unless (zerop (current-column)) (insert "\n"))
+    (insert name "\n")
+    (sort-lines nil (point-min) (point-max))
+    (save-buffer))
+  (when created
+    (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
+  (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
+
+
+;;;; Wrappers for basic git commands
+;;;; ------------------------------------------------------------
+
+(defun git-rev-parse (rev)
+  "Parse a revision name and return its SHA1."
+  (git-get-string-sha1
+   (git-call-process-env-string nil "rev-parse" rev)))
+
+(defun git-repo-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)))
+    (and str (car (split-string str "\n")))))
+
+(defun git-symbolic-ref (ref)
+  "Wrapper for the git-symbolic-ref command."
+  (let ((str (git-call-process-env-string nil "symbolic-ref" ref)))
+    (and str (car (split-string str "\n")))))
+
+(defun git-update-ref (ref val &optional oldval)
+  "Update a reference by calling git-update-ref."
+  (apply #'git-call-process-env nil nil "update-ref" ref val (if oldval (list oldval))))
+
+(defun git-read-tree (tree &optional index-file)
+  "Read a tree into the index file."
+  (apply #'git-call-process-env nil
+         (if index-file `(("GIT_INDEX_FILE" . ,index-file)) nil)
+         "read-tree" (if tree (list tree))))
+
+(defun git-write-tree (&optional index-file)
+  "Call git-write-tree and return the resulting tree SHA1 as a string."
+  (git-get-string-sha1
+   (git-call-process-env-string (and index-file `(("GIT_INDEX_FILE" . ,index-file))) "write-tree")))
+
+(defun git-commit-tree (buffer tree head)
+  "Call git-commit-tree with buffer as input and return the resulting commit SHA1."
+  (let ((author-name (git-get-committer-name))
+        (author-email (git-get-committer-email))
+        author-date log-start log-end args)
+    (when head
+      (push "-p" args)
+      (push head args))
+    (with-current-buffer buffer
+      (goto-char (point-min))
+      (if
+          (setq log-start (re-search-forward (concat "^" (regexp-quote git-log-msg-separator) "\n") nil t))
+          (save-restriction
+            (narrow-to-region (point-min) log-start)
+            (goto-char (point-min))
+            (when (re-search-forward "^Author: +\\(.*?\\) *<\\(.*\\)> *$" nil t)
+              (setq author-name (match-string 1)
+                    author-email (match-string 2)))
+            (goto-char (point-min))
+            (when (re-search-forward "^Date: +\\(.*\\)$" nil t)
+              (setq author-date (match-string 1)))
+            (goto-char (point-min))
+            (while (re-search-forward "^Parent: +\\([0-9a-f]+\\)" nil t)
+              (unless (string-equal head (match-string 1))
+                (push "-p" args)
+                (push (match-string 1) args))))
+        (setq log-start (point-min)))
+      (setq log-end (point-max)))
+    (git-get-string-sha1
+     (with-output-to-string
+       (with-current-buffer standard-output
+         (let ((coding-system-for-write git-commits-coding-system)
+               (env `(("GIT_AUTHOR_NAME" . ,author-name)
+                      ("GIT_AUTHOR_EMAIL" . ,author-email)
+                      ("GIT_COMMITTER_NAME" . ,(git-get-committer-name))
+                      ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email)))))
+           (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env))
+           (apply #'git-run-command-region
+                  buffer log-start log-end env
+                  "commit-tree" tree (nreverse args))))))))
+
+(defun git-empty-db-p ()
+  "Check if the git db is empty (no commit done yet)."
+  (not (eq 0 (call-process "git" nil nil nil "rev-parse" "--verify" "HEAD"))))
+
+(defun git-get-merge-heads ()
+  "Retrieve the merge heads from the MERGE_HEAD file if present."
+  (let (heads)
+    (when (file-readable-p ".git/MERGE_HEAD")
+      (with-temp-buffer
+        (insert-file-contents ".git/MERGE_HEAD" nil nil nil t)
+        (goto-char (point-min))
+        (while (re-search-forward "[0-9a-f]\\{40\\}" nil t)
+          (push (match-string 0) heads))))
+    (nreverse heads)))
+
+;;;; File info structure
+;;;; ------------------------------------------------------------
+
+; fileinfo structure stolen from pcl-cvs
+(defstruct (git-fileinfo
+            (:copier nil)
+            (:constructor git-create-fileinfo (state name &optional old-perm new-perm rename-state orig-name marked))
+            (:conc-name git-fileinfo->))
+  marked              ;; t/nil
+  state               ;; current state
+  name                ;; file name
+  old-perm new-perm   ;; permission flags
+  rename-state        ;; rename or copy state
+  orig-name           ;; original name for renames or copies
+  needs-refresh)      ;; whether file needs to be refreshed
+
+(defvar git-status nil)
+
+(defun git-clear-status (status)
+  "Remove everything from the status list."
+  (ewoc-filter status (lambda (info) nil)))
+
+(defun git-set-files-state (files state)
+  "Set the state of a list of files."
+  (dolist (info files)
+    (unless (eq (git-fileinfo->state info) state)
+      (setf (git-fileinfo->state info) state)
+      (setf (git-fileinfo->rename-state info) nil)
+      (setf (git-fileinfo->orig-name info) nil)
+      (setf (git-fileinfo->needs-refresh info) t))))
+
+(defun git-state-code (code)
+  "Convert from a string to a added/deleted/modified state."
+  (case (string-to-char code)
+    (?M 'modified)
+    (?? 'unknown)
+    (?A 'added)
+    (?D 'deleted)
+    (?U 'unmerged)
+    (t nil)))
+
+(defun git-status-code-as-string (code)
+  "Format a git status code as string."
+  (case code
+    ('modified (propertize "Modified" 'face 'git-status-face))
+    ('unknown  (propertize "Unknown " 'face 'git-unknown-face))
+    ('added    (propertize "Added   " 'face 'git-status-face))
+    ('deleted  (propertize "Deleted " 'face 'git-status-face))
+    ('unmerged (propertize "Unmerged" 'face 'git-unmerged-face))
+    ('uptodate (propertize "Uptodate" 'face 'git-uptodate-face))
+    ('ignored  (propertize "Ignored " 'face 'git-ignored-face))
+    (t "?       ")))
+
+(defun git-rename-as-string (info)
+  "Return a string describing the copy or rename associated with INFO, or an empty string if none."
+  (let ((state (git-fileinfo->rename-state info)))
+    (if state
+        (propertize
+         (concat "   ("
+                 (if (eq state 'copy) "copied from "
+                   (if (eq (git-fileinfo->state info) 'added) "renamed to "
+                     "renamed from "))
+                 (git-escape-file-name (git-fileinfo->orig-name info))
+                 ")") 'face 'git-status-face)
+      "")))
+
+(defun git-permissions-as-string (old-perm new-perm)
+  "Format a permission change as string."
+  (propertize
+   (if (or (not old-perm)
+           (not new-perm)
+           (eq 0 (logand ?\111 (logxor old-perm new-perm))))
+       "  "
+     (if (eq 0 (logand ?\111 old-perm)) "+x" "-x"))
+  'face 'git-permission-face))
+
+(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))
+                  (git-rename-as-string info))))
+
+(defun git-parse-status (status)
+  "Parse the output of git-diff-index in the current buffer."
+  (goto-char (point-min))
+  (while (re-search-forward
+          ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
+          nil t 1)
+    (let ((old-perm (string-to-number (match-string 1) 8))
+          (new-perm (string-to-number (match-string 2) 8))
+          (state (or (match-string 4) (match-string 6)))
+          (name (or (match-string 5) (match-string 7)))
+          (new-name (match-string 8)))
+      (if new-name  ; copy or rename
+          (if (eq ?C (string-to-char state))
+              (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))
+            (ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))
+            (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))
+        (ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))
+
+(defun git-find-status-file (status file)
+  "Find a given file in the status ewoc and return its node."
+  (let ((node (ewoc-nth status 0)))
+    (while (and node (not (string= file (git-fileinfo->name (ewoc-data node)))))
+      (setq node (ewoc-next status node)))
+    node))
+
+(defun git-parse-ls-files (status default-state &optional skip-existing)
+  "Parse the output of git-ls-files in the current buffer."
+  (goto-char (point-min))
+  (let (infolist)
+    (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
+      (let ((state (match-string 1))
+            (name (match-string 2)))
+        (unless (and skip-existing (git-find-status-file status name))
+          (push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))
+    (dolist (info (nreverse infolist))
+      (ewoc-enter-last status info))))
+
+(defun git-parse-ls-unmerged (status)
+  "Parse the output of git-ls-files -u in the current buffer."
+  (goto-char (point-min))
+  (let (files)
+    (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
+      (let ((node (git-find-status-file status (match-string 1))))
+        (when node (push (ewoc-data node) files))))
+    (git-set-files-state files 'unmerged)))
+
+(defun git-add-status-file (state name)
+  "Add a new file to the status list (if not existing already) and return its node."
+  (unless git-status (error "Not in git-status buffer."))
+  (or (git-find-status-file git-status name)
+      (ewoc-enter-last git-status (git-create-fileinfo state name))))
+
+(defun git-marked-files ()
+  "Return a list of all marked files, or if none a list containing just the file at cursor position."
+  (unless git-status (error "Not in git-status buffer."))
+  (or (ewoc-collect git-status (lambda (info) (git-fileinfo->marked info)))
+      (list (ewoc-data (ewoc-locate git-status)))))
+
+(defun git-marked-files-state (&rest states)
+  "Return marked files that are in the specified states."
+  (let ((files (git-marked-files))
+        result)
+    (dolist (info files)
+      (when (memq (git-fileinfo->state info) states)
+        (push info result)))
+    result))
+
+(defun git-refresh-files ()
+  "Refresh all files that need it and clear the needs-refresh flag."
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map
+   (lambda (info)
+     (let ((refresh (git-fileinfo->needs-refresh info)))
+       (setf (git-fileinfo->needs-refresh info) nil)
+       refresh))
+   git-status)
+  ; move back to goal column
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-refresh-ewoc-hf (status)
+  "Refresh the ewoc header and footer."
+  (let ((branch (git-symbolic-ref "HEAD"))
+        (head (if (git-empty-db-p) "Nothing committed yet"
+                (substring (git-rev-parse "HEAD") 0 10)))
+        (merge-heads (git-get-merge-heads)))
+    (ewoc-set-hf status
+                 (format "Directory:  %s\nBranch:     %s\nHead:       %s%s\n"
+                         default-directory
+                         (if (string-match "^refs/heads/" branch)
+                             (substring branch (match-end 0))
+                           branch)
+                         head
+                         (if merge-heads
+                             (concat "\nMerging:    "
+                                     (mapconcat (lambda (str) (substring str 0 10)) merge-heads " "))
+                           ""))
+                 (if (ewoc-nth status 0) "" "    No changes."))))
+
+(defun git-get-filenames (files)
+  (mapcar (lambda (info) (git-fileinfo->name info)) files))
+
+(defun git-update-index (index-file files)
+  "Run git-update-index on a list of files."
+  (let ((env (and index-file `(("GIT_INDEX_FILE" . ,index-file))))
+        added deleted modified)
+    (dolist (info files)
+      (case (git-fileinfo->state info)
+        ('added (push info added))
+        ('deleted (push info deleted))
+        ('modified (push info modified))))
+    (when added
+      (apply #'git-run-command nil env "update-index" "--add" "--" (git-get-filenames added)))
+    (when deleted
+      (apply #'git-run-command nil env "update-index" "--remove" "--" (git-get-filenames deleted)))
+    (when modified
+      (apply #'git-run-command nil env "update-index" "--" (git-get-filenames modified)))))
+
+(defun git-do-commit ()
+  "Perform the actual commit using the current buffer as log message."
+  (interactive)
+  (let ((buffer (current-buffer))
+        (index-file (make-temp-file "gitidx")))
+    (with-current-buffer log-edit-parent-buffer
+      (if (git-marked-files-state 'unmerged)
+          (message "You cannot commit unmerged files, resolve them first.")
+        (unwind-protect
+            (let ((files (git-marked-files-state 'added 'deleted 'modified))
+                  head head-tree)
+              (unless (git-empty-db-p)
+                (setq head (git-rev-parse "HEAD")
+                      head-tree (git-rev-parse "HEAD^{tree}")))
+              (if files
+                  (progn
+                    (git-read-tree head-tree index-file)
+                    (git-update-index nil files)         ;update both the default index
+                    (git-update-index index-file files)  ;and the temporary one
+                    (let ((tree (git-write-tree index-file)))
+                      (if (or (not (string-equal tree head-tree))
+                              (yes-or-no-p "The tree was not modified, do you really want to perform an empty commit? "))
+                          (let ((commit (git-commit-tree buffer tree head)))
+                            (git-update-ref "HEAD" commit head)
+                            (condition-case nil (delete-file ".git/MERGE_HEAD") (error nil))
+                            (with-current-buffer buffer (erase-buffer))
+                            (git-set-files-state files 'uptodate)
+                            (git-refresh-files)
+                            (git-refresh-ewoc-hf git-status)
+                            (message "Committed %s." commit))
+                        (message "Commit aborted."))))
+                (message "No files to commit.")))
+          (delete-file index-file))))))
+
+
+;;;; Interactive functions
+;;;; ------------------------------------------------------------
+
+(defun git-mark-file ()
+  "Mark the file that the cursor is on and move to the next one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) t)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-next git-status 1)))
+
+(defun git-unmark-file ()
+  "Unmark the file that the cursor is on and move to the next one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) nil)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-next git-status 1)))
+
+(defun git-unmark-file-up ()
+  "Unmark the file that the cursor is on and move to the previous one."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let* ((pos (ewoc-locate git-status))
+         (info (ewoc-data pos)))
+    (setf (git-fileinfo->marked info) nil)
+    (ewoc-invalidate git-status pos)
+    (ewoc-goto-prev git-status 1)))
+
+(defun git-mark-all ()
+  "Mark all files."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-unmark-all ()
+  "Unmark all files."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-toggle-all-marks ()
+  "Toggle all file marks."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) (not (git-fileinfo->marked info))) t) git-status)
+  ; move back to goal column after invalidate
+  (when goal-column (move-to-column goal-column)))
+
+(defun git-next-file (&optional n)
+  "Move the selection down N files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-goto-next git-status n))
+
+(defun git-prev-file (&optional n)
+  "Move the selection up N files."
+  (interactive "p")
+  (unless git-status (error "Not in git-status buffer."))
+  (ewoc-goto-prev git-status n))
+
+(defun git-add-file ()
+  "Add marked file(s) to the index cache."
+  (interactive)
+  (let ((files (git-marked-files-state 'unknown)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'added (file-relative-name
+                                          (read-file-name "File to add: " nil nil t))))
+            files))
+    (apply #'git-run-command nil nil "update-index" "--info-only" "--add" "--" (git-get-filenames files))
+    (git-set-files-state files 'added)
+    (git-refresh-files)))
+
+(defun git-ignore-file ()
+  "Add marked file(s) to the ignore list."
+  (interactive)
+  (let ((files (git-marked-files-state 'unknown)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'unknown (file-relative-name
+                                            (read-file-name "File to ignore: " nil nil t))))
+            files))
+    (dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))
+    (git-set-files-state files 'ignored)
+    (git-refresh-files)))
+
+(defun git-remove-file ()
+  "Remove the marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))
+    (unless files
+      (push (ewoc-data
+             (git-add-status-file 'unknown (file-relative-name
+                                            (read-file-name "File to remove: " nil nil t))))
+            files))
+    (if (yes-or-no-p
+         (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" "")))
+        (progn
+          (dolist (info files)
+            (let ((name (git-fileinfo->name info)))
+              (when (file-exists-p name) (delete-file name))))
+          (apply #'git-run-command nil nil "update-index" "--info-only" "--remove" "--" (git-get-filenames files))
+          ; remove unknown files from the list, set the others to deleted
+          (ewoc-filter git-status
+                       (lambda (info files)
+                         (not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))
+                       files)
+          (git-set-files-state files 'deleted)
+          (git-refresh-files)
+          (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
+            (git-refresh-ewoc-hf git-status)))
+      (message "Aborting"))))
+
+(defun git-revert-file ()
+  "Revert changes to the marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files))
+        added modified)
+    (when (and files
+               (yes-or-no-p
+                (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
+      (dolist (info files)
+        (case (git-fileinfo->state info)
+          ('added (push info added))
+          ('deleted (push info modified))
+          ('unmerged (push info modified))
+          ('modified (push info modified))))
+      (when added
+          (apply #'git-run-command nil nil "update-index" "--force-remove" "--" (git-get-filenames added))
+          (git-set-files-state added 'unknown))
+      (when modified
+          (apply #'git-run-command nil nil "checkout" "HEAD" (git-get-filenames modified))
+          (git-set-files-state modified 'uptodate))
+      (git-refresh-files))))
+
+(defun git-resolve-file ()
+  "Resolve conflicts in marked file(s)."
+  (interactive)
+  (let ((files (git-marked-files-state 'unmerged)))
+    (when files
+      (apply #'git-run-command nil nil "update-index" "--info-only" "--" (git-get-filenames files))
+      (git-set-files-state files 'modified)
+      (git-refresh-files))))
+
+(defun git-remove-handled ()
+  "Remove handled files from the status list."
+  (interactive)
+  (ewoc-filter git-status
+               (lambda (info)
+                 (not (or (eq (git-fileinfo->state info) 'ignored)
+                          (eq (git-fileinfo->state info) 'uptodate)))))
+  (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
+    (git-refresh-ewoc-hf git-status)))
+
+(defun git-setup-diff-buffer (buffer)
+  "Setup a buffer for displaying a diff."
+  (with-current-buffer buffer
+    (diff-mode)
+    (goto-char (point-min))
+    (setq buffer-read-only t))
+  (display-buffer buffer)
+  (shrink-window-if-larger-than-buffer))
+
+(defun git-diff-file ()
+  "Diff the marked file(s) against HEAD."
+  (interactive)
+  (let ((files (git-marked-files)))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M" "HEAD" "--" (git-get-filenames files)))))
+
+(defun git-diff-file-merge-head (arg)
+  "Diff the marked file(s) against the first merge head (or the nth one with a numeric prefix)."
+  (interactive "p")
+  (let ((files (git-marked-files))
+        (merge-heads (git-get-merge-heads)))
+    (unless merge-heads (error "No merge in progress"))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-index" "-p" "-M"
+            (or (nth (1- arg) merge-heads) "HEAD") "--" (git-get-filenames files)))))
+
+(defun git-diff-unmerged-file (stage)
+  "Diff the marked unmerged file(s) against the specified stage."
+  (let ((files (git-marked-files)))
+    (git-setup-diff-buffer
+     (apply #'git-run-command-buffer "*git-diff*" "diff-files" "-p" stage "--" (git-get-filenames files)))))
+
+(defun git-diff-file-base ()
+  "Diff the marked unmerged file(s) against the common base file."
+  (interactive)
+  (git-diff-unmerged-file "-1"))
+
+(defun git-diff-file-mine ()
+  "Diff the marked unmerged file(s) against my pre-merge version."
+  (interactive)
+  (git-diff-unmerged-file "-2"))
+
+(defun git-diff-file-other ()
+  "Diff the marked unmerged file(s) against the other's pre-merge version."
+  (interactive)
+  (git-diff-unmerged-file "-3"))
+
+(defun git-diff-file-combined ()
+  "Do a combined diff of the marked unmerged file(s)."
+  (interactive)
+  (git-diff-unmerged-file "-c"))
+
+(defun git-diff-file-idiff ()
+  "Perform an interactive diff on the current file."
+  (interactive)
+  (error "Interactive diffs not implemented yet."))
+
+(defun git-log-file ()
+  "Display a log of changes to the marked file(s)."
+  (interactive)
+  (let* ((files (git-marked-files))
+         (coding-system-for-read git-commits-coding-system)
+         (buffer (apply #'git-run-command-buffer "*git-log*" "rev-list" "--pretty" "HEAD" "--" (git-get-filenames files))))
+    (with-current-buffer buffer
+      ; (git-log-mode)  FIXME: implement log mode
+      (goto-char (point-min))
+      (setq buffer-read-only t))
+    (display-buffer buffer)))
+
+(defun git-log-edit-files ()
+  "Return a list of marked files for use in the log-edit buffer."
+  (with-current-buffer log-edit-parent-buffer
+    (git-get-filenames (git-marked-files-state 'added 'deleted 'modified))))
+
+(defun git-commit-file ()
+  "Commit the marked file(s), asking for a commit message."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((buffer (get-buffer-create "*git-commit*"))
+        (merge-heads (git-get-merge-heads))
+        (dir default-directory)
+        (sign-off git-append-signed-off-by))
+    (with-current-buffer buffer
+      (when (eq 0 (buffer-size))
+        (cd dir)
+        (erase-buffer)
+        (insert
+         (propertize
+          (format "Author: %s <%s>\n%s"
+                  (git-get-committer-name) (git-get-committer-email)
+                  (if merge-heads
+                      (format "Parent: %s\n%s\n"
+                              (git-rev-parse "HEAD")
+                              (mapconcat (lambda (str) (concat "Parent: " str)) merge-heads "\n"))
+                    ""))
+          'face 'git-header-face)
+         (propertize git-log-msg-separator 'face 'git-separator-face)
+         "\n")
+        (cond ((and merge-heads (file-readable-p ".git/MERGE_MSG"))
+               (insert-file-contents ".git/MERGE_MSG"))
+              (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))))
+
+(defun git-find-file ()
+  "Visit the current file in its own buffer."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file (git-fileinfo->name info))
+    (when (eq 'unmerged (git-fileinfo->state info))
+      (smerge-mode))))
+
+(defun git-find-file-imerge ()
+  "Visit the current file in interactive merge mode."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (find-file (git-fileinfo->name info))
+    (smerge-ediff)))
+
+(defun git-view-file ()
+  "View the current file in its own buffer."
+  (interactive)
+  (unless git-status (error "Not in git-status buffer."))
+  (let ((info (ewoc-data (ewoc-locate git-status))))
+    (view-file (git-fileinfo->name info))))
+
+(defun git-refresh-status ()
+  "Refresh the git status buffer."
+  (interactive)
+  (let* ((status git-status)
+         (pos (ewoc-locate status))
+         (cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))
+    (unless status (error "Not in git-status buffer."))
+    (git-clear-status status)
+    (git-run-command nil nil "update-index" "--info-only" "--refresh")
+    (if (git-empty-db-p)
+        ; we need some special handling for an empty db
+        (with-temp-buffer
+          (git-run-command t nil "ls-files" "-z" "-t" "-c")
+          (git-parse-ls-files status 'added))
+      (with-temp-buffer
+        (git-run-command t nil "diff-index" "-z" "-M" "HEAD")
+        (git-parse-status status)))
+      (with-temp-buffer
+        (git-run-command t nil "ls-files" "-z" "-u")
+        (git-parse-ls-unmerged status))
+      (when (file-readable-p ".git/info/exclude")
+        (with-temp-buffer
+          (git-run-command t nil "ls-files" "-z" "-t" "-o"
+                           "--exclude-from=.git/info/exclude"
+                           (concat "--exclude-per-directory=" git-per-dir-ignore-file))
+          (git-parse-ls-files status 'unknown)))
+    (git-refresh-files)
+    (git-refresh-ewoc-hf status)
+    ; move point to the current file name if any
+    (let ((node (and cur-name (git-find-status-file status cur-name))))
+      (when node (ewoc-goto-node status node)))))
+
+(defun git-status-quit ()
+  "Quit git-status mode."
+  (interactive)
+  (bury-buffer))
+
+;;;; Major Mode
+;;;; ------------------------------------------------------------
+
+(defvar git-status-mode-hook nil
+  "Run after `git-status-mode' is setup.")
+
+(defvar git-status-mode-map nil
+  "Keymap for git major mode.")
+
+(defvar git-status nil
+  "List of all files managed by the git-status mode.")
+
+(unless git-status-mode-map
+  (let ((map (make-keymap))
+        (diff-map (make-sparse-keymap)))
+    (suppress-keymap map)
+    (define-key map " "   'git-next-file)
+    (define-key map "a"   'git-add-file)
+    (define-key map "c"   'git-commit-file)
+    (define-key map "d"    diff-map)
+    (define-key map "="   'git-diff-file)
+    (define-key map "f"   'git-find-file)
+    (define-key map "\r"  'git-find-file)
+    (define-key map "g"   'git-refresh-status)
+    (define-key map "i"   'git-ignore-file)
+    (define-key map "l"   'git-log-file)
+    (define-key map "m"   'git-mark-file)
+    (define-key map "M"   'git-mark-all)
+    (define-key map "n"   'git-next-file)
+    (define-key map "p"   'git-prev-file)
+    (define-key map "q"   'git-status-quit)
+    (define-key map "r"   'git-remove-file)
+    (define-key map "R"   'git-resolve-file)
+    (define-key map "T"   'git-toggle-all-marks)
+    (define-key map "u"   'git-unmark-file)
+    (define-key map "U"   'git-revert-file)
+    (define-key map "v"   'git-view-file)
+    (define-key map "x"   'git-remove-handled)
+    (define-key map "\C-?" 'git-unmark-file-up)
+    (define-key map "\M-\C-?" 'git-unmark-all)
+    ; the diff submap
+    (define-key diff-map "b" 'git-diff-file-base)
+    (define-key diff-map "c" 'git-diff-file-combined)
+    (define-key diff-map "=" 'git-diff-file)
+    (define-key diff-map "e" 'git-diff-file-idiff)
+    (define-key diff-map "E" 'git-find-file-imerge)
+    (define-key diff-map "h" 'git-diff-file-merge-head)
+    (define-key diff-map "m" 'git-diff-file-mine)
+    (define-key diff-map "o" 'git-diff-file-other)
+    (setq git-status-mode-map map)))
+
+;; git mode should only run in the *git status* buffer
+(put 'git-status-mode 'mode-class 'special)
+
+(defun git-status-mode ()
+  "Major mode for interacting with Git.
+Commands:
+\\{git-status-mode-map}"
+  (kill-all-local-variables)
+  (buffer-disable-undo)
+  (setq mode-name "git status"
+        major-mode 'git-status-mode
+        goal-column 17
+        buffer-read-only t)
+  (use-local-map git-status-mode-map)
+  (let ((buffer-read-only nil))
+    (erase-buffer)
+  (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
+    (set (make-local-variable 'git-status) status))
+  (set (make-local-variable 'list-buffers-directory) default-directory)
+  (run-hooks 'git-status-mode-hook)))
+
+(defun git-status (dir)
+  "Entry point into git-status mode."
+  (interactive "DSelect directory: ")
+  (setq dir (git-get-top-dir dir))
+  (if (file-directory-p (concat (file-name-as-directory dir) ".git"))
+      (let ((buffer (create-file-buffer (expand-file-name "*git-status*" dir))))
+        (switch-to-buffer buffer)
+        (cd dir)
+        (git-status-mode)
+        (git-refresh-status)
+        (goto-char (point-min)))
+    (message "%s is not a git working tree." dir)))
+
+(provide 'git)
+;;; git.el ends here
diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el
new file mode 100644 (file)
index 0000000..2453cdc
--- /dev/null
@@ -0,0 +1,135 @@
+;;; vc-git.el --- VC backend for the git version control system
+
+;; Copyright (C) 2006 Alexandre Julliard
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; This file contains a VC backend for the git version control
+;; system.
+;;
+;; To install: put this file on the load-path and add GIT to the list
+;; of supported backends in `vc-handled-backends'.
+;;
+;; TODO
+;;  - changelog generation
+;;  - working with revisions other than HEAD
+;;
+
+(defvar git-commits-coding-system 'utf-8
+  "Default coding system for git commits.")
+
+(defun vc-git--run-command-string (file &rest args)
+  "Run a git command on FILE and return its output as string."
+  (let* ((ok t)
+         (str (with-output-to-string
+                (with-current-buffer standard-output
+                  (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil
+                                       (append args (list (file-relative-name file)))))
+                    (setq ok nil))))))
+    (and ok str)))
+
+(defun vc-git--run-command (file &rest args)
+  "Run a git command on FILE, discarding any output."
+  (let ((name (file-relative-name file)))
+    (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name))))))
+
+(defun vc-git-registered (file)
+  "Check whether FILE is registered with git."
+  (with-temp-buffer
+    (let* ((dir (file-name-directory file))
+           (name (file-relative-name file dir)))
+      (when dir (cd dir))
+      (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))
+           (let ((str (buffer-string)))
+             (and (> (length str) (length name))
+                  (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
+
+(defun vc-git-state (file)
+  "git-specific version of `vc-state'."
+  (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--")))
+    (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff))
+        'edited
+      'up-to-date)))
+
+(defun vc-git-workfile-version (file)
+  "git-specific version of `vc-workfile-version'."
+  (let ((str (with-output-to-string
+               (with-current-buffer standard-output
+                 (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD")))))
+    (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str)
+        (match-string 2 str)
+      str)))
+
+(defun vc-git-revert (file &optional contents-done)
+  "Revert FILE to the version stored in the git repository."
+  (if contents-done
+      (vc-git--run-command file "update-index" "--")
+    (vc-git--run-command file "checkout" "HEAD")))
+
+(defun vc-git-checkout-model (file)
+  'implicit)
+
+(defun vc-git-workfile-unchanged-p (file)
+  (let ((sha1 (vc-git--run-command-string file "hash-object" "--"))
+        (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--")))
+    (and head
+         (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head)
+         (string= (car (split-string sha1 "\n")) (match-string 1 head)))))
+
+(defun vc-git-register (file &optional rev comment)
+  "Register FILE into the git version-control system."
+  (vc-git--run-command file "update-index" "--add" "--"))
+
+(defun vc-git-print-log (file)
+  (let ((name (file-relative-name file))
+        (coding-system-for-read git-commits-coding-system))
+    (vc-do-command nil 'async "git" name "rev-list" "--pretty" "HEAD" "--")))
+
+(defun vc-git-diff (file &optional rev1 rev2)
+  (let ((name (file-relative-name file)))
+    (if (and rev1 rev2)
+        (vc-do-command "*vc-diff*" 0 "git" name "diff-tree" "-p" rev1 rev2 "--")
+      (vc-do-command "*vc-diff*" 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--"))
+    ; git-diff-index doesn't set exit status like diff does
+    (if (vc-git-workfile-unchanged-p file) 0 1)))
+
+(defun vc-git-checkin (file rev comment)
+  (let ((coding-system-for-write git-commits-coding-system))
+    (vc-git--run-command file "commit" "-m" comment "--only" "--")))
+
+(defun vc-git-checkout (file &optional editable rev destfile)
+  (vc-git--run-command file "checkout" (or rev "HEAD")))
+
+(defun vc-git-annotate-command (file buf &optional rev)
+  ; FIXME: rev is ignored
+  (let ((name (file-relative-name file)))
+    (call-process "git" nil buf nil "annotate" name)))
+
+(defun vc-git-annotate-time ()
+  (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+       (vc-annotate-convert-time
+        (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
+
+;; Not really useful since we can't do anything with the revision yet
+;;(defun vc-annotate-extract-revision-at-line ()
+;;  (save-excursion
+;;    (move-beginning-of-line 1)
+;;    (and (looking-at "[0-9a-f]+")
+;;         (buffer-substring (match-beginning 0) (match-end 0)))))
+
+(provide 'vc-git)
diff --git a/contrib/git-svn/.gitignore b/contrib/git-svn/.gitignore
new file mode 100644 (file)
index 0000000..d8d87e3
--- /dev/null
@@ -0,0 +1,4 @@
+git-svn
+git-svn.xml
+git-svn.html
+git-svn.1
diff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile
new file mode 100644 (file)
index 0000000..48f60b3
--- /dev/null
@@ -0,0 +1,36 @@
+all: git-svn
+
+prefix?=$(HOME)
+bindir=$(prefix)/bin
+mandir=$(prefix)/man
+man1=$(mandir)/man1
+INSTALL?=install
+doc_conf=../../Documentation/asciidoc.conf
+-include ../../config.mak
+
+git-svn: git-svn.perl
+       cp $< $@
+       chmod +x $@
+
+install: all
+       $(INSTALL) -d -m755 $(DESTDIR)$(bindir)
+       $(INSTALL) git-svn $(DESTDIR)$(bindir)
+
+install-doc: doc
+       $(INSTALL) git-svn.1 $(DESTDIR)$(man1)
+
+doc: git-svn.1
+git-svn.1 : git-svn.xml
+       xmlto man git-svn.xml
+git-svn.xml : git-svn.txt
+       asciidoc -b docbook -d manpage \
+               -f ../../Documentation/asciidoc.conf $<
+git-svn.html : git-svn.txt
+       asciidoc -b xhtml11 -d manpage \
+               -f ../../Documentation/asciidoc.conf $<
+test: git-svn
+       cd t && $(SHELL) ./t0000-contrib-git-svn.sh
+       cd t && $(SHELL) ./t0001-contrib-git-svn-props.sh
+
+clean:
+       rm -f git-svn *.xml *.html *.1
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
new file mode 100755 (executable)
index 0000000..aac8779
--- /dev/null
@@ -0,0 +1,1249 @@
+#!/usr/bin/env perl
+# Copyright (C) 2006, Eric Wong <normalperson@yhbt.net>
+# License: GPL v2 or later
+use warnings;
+use strict;
+use vars qw/   $AUTHOR $VERSION
+               $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
+               $GIT_SVN_INDEX $GIT_SVN
+               $GIT_DIR $REV_DIR/;
+$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
+$VERSION = '1.1.0-pre';
+
+use Cwd qw/abs_path/;
+$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
+$ENV{GIT_DIR} = $GIT_DIR;
+
+# make sure the svn binary gives consistent output between locales and TZs:
+$ENV{TZ} = 'UTC';
+$ENV{LC_ALL} = 'C';
+
+# 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:
+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/;
+use File::Spec qw//;
+use POSIX qw/strftime/;
+my $sha1 = qr/[a-f\d]{40}/;
+my $sha1_short = qr/[a-f\d]{4,40}/;
+my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
+       $_find_copies_harder, $_l, $_version, $_upgrade, $_authors);
+my (@_branch_from, %tree_map, %users);
+my $_svn_co_url_revs;
+
+my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
+               'branch|b=s' => \@_branch_from,
+               'authors-file|A=s' => \$_authors );
+
+# yes, 'native' sets "\n".  Patches to fix this for non-*nix systems welcome:
+my %EOL = ( CR => "\015", LF => "\012", CRLF => "\015\012", native => "\012" );
+
+my %cmd = (
+       fetch => [ \&fetch, "Download new revisions from SVN",
+                       { 'revision|r=s' => \$_revision, %fc_opts } ],
+       init => [ \&init, "Initialize a repo for tracking" .
+                         " (requires URL argument)", { } ],
+       commit => [ \&commit, "Commit git revisions to SVN",
+                       {       'stdin|' => \$_stdin,
+                               'edit|e' => \$_edit,
+                               'rmdir' => \$_rmdir,
+                               'find-copies-harder' => \$_find_copies_harder,
+                               'l=i' => \$_l,
+                               %fc_opts,
+                       } ],
+       'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ],
+       rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
+                       { 'no-ignore-externals' => \$_no_ignore_ext,
+                         'upgrade' => \$_upgrade } ],
+);
+my $cmd;
+for (my $i = 0; $i < @ARGV; $i++) {
+       if (defined $cmd{$ARGV[$i]}) {
+               $cmd = $ARGV[$i];
+               splice @ARGV, $i, 1;
+               last;
+       }
+};
+
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+# convert GetOpt::Long specs for use by git-repo-config
+foreach my $o (keys %opts) {
+       my $v = $opts{$o};
+       my ($key) = ($o =~ /^([a-z\-]+)/);
+       $key =~ s/-//g;
+       my $arg = 'git-repo-config';
+       $arg .= ' --int' if ($o =~ /=i$/);
+       $arg .= ' --bool' if ($o !~ /=[sfi]$/);
+       if (ref $v eq 'ARRAY') {
+               chomp(my @tmp = `$arg --get-all svn.$key`);
+               @$v = @tmp if @tmp;
+       } else {
+               chomp(my $tmp = `$arg --get svn.$key`);
+               if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
+                       $$v = $tmp;
+               }
+       }
+}
+
+GetOptions(%opts, 'help|H|h' => \$_help,
+               'version|V' => \$_version,
+               'id|i=s' => \$GIT_SVN) or exit 1;
+
+$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
+$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
+$SVN_URL = undef;
+$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
+$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
+
+usage(0) if $_help;
+version() if $_version;
+usage(1) unless defined $cmd;
+load_authors() if $_authors;
+svn_compat_check();
+$cmd{$cmd}->[0]->(@ARGV);
+exit 0;
+
+####################### primary functions ######################
+sub usage {
+       my $exit = shift || 0;
+       my $fd = $exit ? \*STDERR : \*STDOUT;
+       print $fd <<"";
+git-svn - bidirectional operations between a single Subversion tree and git
+Usage: $0 <command> [options] [arguments]\n
+
+       print $fd "Available commands:\n" unless $cmd;
+
+       foreach (sort keys %cmd) {
+               next if $cmd && $cmd ne $_;
+               print $fd '  ',pack('A13',$_),$cmd{$_}->[1],"\n";
+               foreach (keys %{$cmd{$_}->[2]}) {
+                       # prints out arguments as they should be passed:
+                       my $x = s#=s$## ? '<arg>' : s#=i$## ? '<num>' : '';
+                       print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+                                                       "--$_" : "-$_" }
+                                               split /\|/,$_)," $x\n";
+               }
+       }
+       print $fd <<"";
+\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
+arbitrary identifier if you're tracking multiple SVN branches/repositories in
+one git repository and want to keep them separate.  See git-svn(1) for more
+information.
+
+       exit $exit;
+}
+
+sub version {
+       print "git-svn version $VERSION\n";
+       exit 0;
+}
+
+sub rebuild {
+       $SVN_URL = shift or undef;
+       my $newest_rev = 0;
+       if ($_upgrade) {
+               sys('git-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 $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`);
+               next if (!@commit); # skip merges
+               my $id = $commit[$#commit];
+               my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+                                               \s([a-f\d\-]+)$/x);
+               if (!$rev || !$uuid || !$url) {
+                       # some of the original repositories I made had
+                       # indentifiers like this:
+                       ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)
+                                                       \@([a-f\d\-]+)/x);
+                       if (!$rev || !$uuid) {
+                               croak "Unable to extract revision or UUID from ",
+                                       "$c, $id\n";
+                       }
+               }
+
+               # if we merged or otherwise started elsewhere, this is
+               # how we break out of it
+               next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
+               next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
+
+               print "r$rev = $c\n";
+               unless (defined $latest) {
+                       if (!$SVN_URL && !$url) {
+                               croak "SVN repository location required: $url\n";
+                       }
+                       $SVN_URL ||= $url;
+                       $SVN_UUID ||= $uuid;
+                       setup_git_svn();
+                       $latest = $rev;
+               }
+               assert_revision_eq_or_unknown($rev, $c);
+               sys('git-update-ref',"$GIT_SVN/revs/$rev",$c);
+               $newest_rev = $rev if ($rev > $newest_rev);
+       }
+       close $rev_list or croak $?;
+       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');
+       }
+       waitpid $pid, 0;
+
+       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
+
+       }
+}
+
+sub init {
+       $SVN_URL = shift or die "SVN repository location required " .
+                               "as a command-line argument\n";
+       unless (-d $GIT_DIR) {
+               sys('git-init-db');
+       }
+       setup_git_svn();
+}
+
+sub fetch {
+       my (@parents) = @_;
+       check_upgrade_needed();
+       $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url");
+       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";
+       my $last_commit = undef;
+       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();
+               $last_commit = file_to_s("$REV_DIR/$base->{revision}");
+       }
+       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)) {
+               assert_tree($last_commit);
+               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;
+       }
+       unless (-e "$GIT_DIR/refs/heads/master") {
+               sys(qw(git-update-ref refs/heads/master),$last_commit);
+       }
+       return $last;
+}
+
+sub commit {
+       my (@commits) = @_;
+       check_upgrade_needed();
+       if ($_stdin || !@commits) {
+               print "Reading from stdin...\n";
+               @commits = ();
+               while (<STDIN>) {
+                       if (/\b($sha1_short)\b/o) {
+                               unshift @commits, $1;
+                       }
+               }
+       }
+       my @revs;
+       foreach my $c (@commits) {
+               chomp(my @tmp = safe_qx('git-rev-parse',$c));
+               if (scalar @tmp == 1) {
+                       push @revs, $tmp[0];
+               } elsif (scalar @tmp > 1) {
+                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
+               } else {
+                       die "Failed to rev-parse $c\n";
+               }
+       }
+       chomp @revs;
+
+       fetch();
+       chdir $SVN_WC or croak $!;
+       my $info = svn_info('.');
+       read_uuid($info);
+       my $svn_current_rev =  $info->{'Last Changed Rev'};
+       foreach my $c (@revs) {
+               my $mods = svn_checkout_tree($svn_current_rev, $c);
+               if (scalar @$mods == 0) {
+                       print "Skipping, no changes detected\n";
+                       next;
+               }
+               $svn_current_rev = svn_commit_tree($svn_current_rev, $c);
+       }
+       print "Done committing ",scalar @revs," revisions to SVN\n";
+}
+
+sub show_ignore {
+       require File::Find or die $!;
+       my $exclude_file = "$GIT_DIR/info/exclude";
+       open my $fh, '<', $exclude_file or croak $!;
+       chomp(my @excludes = (<$fh>));
+       close $fh or croak $!;
+
+       $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url");
+       chdir $SVN_WC or croak $!;
+       my %ign;
+       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
+               s#^\./##;
+               @{$ign{$_}} = safe_qx(qw(svn propget 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/ }
+       }
+}
+
+########################### utility functions #########################
+
+sub read_uuid {
+       return if $SVN_UUID;
+       my $info = shift || svn_info('.');
+       $SVN_UUID = $info->{'Repository UUID'} or
+                                       croak "Repository UUID unreadable\n";
+       s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid");
+}
+
+sub setup_git_svn {
+       defined $SVN_URL or croak "SVN repository location required\n";
+       unless (-d $GIT_DIR) {
+               croak "GIT_DIR=$GIT_DIR does not exist!\n";
+       }
+       mkpath(["$GIT_DIR/$GIT_SVN"]);
+       mkpath(["$GIT_DIR/$GIT_SVN/info"]);
+       mkpath([$REV_DIR]);
+       s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url");
+
+       open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!;
+       print $fd '.svn',"\n";
+       close $fd or croak $!;
+}
+
+sub assert_svn_wc_clean {
+       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);
+       if (scalar @status) {
+               print STDERR "Tree ($SVN_WC) is not clean:\n";
+               print STDERR $_ foreach @status;
+               croak;
+       }
+}
+
+sub assert_tree {
+       my ($treeish) = @_;
+       croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
+       chomp(my $type = `git-cat-file -t $treeish`);
+       my $expected;
+       while ($type eq 'tag') {
+               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+       }
+       if ($type eq 'commit') {
+               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+               ($expected) = ($expected =~ /^tree ($sha1)$/);
+               die "Unable to get tree from $treeish\n" unless $expected;
+       } elsif ($type eq 'tree') {
+               $expected = $treeish;
+       } else {
+               die "$treeish is a $type, expected tree, tag or commit\n";
+       }
+
+       my $old_index = $ENV{GIT_INDEX_FILE};
+       my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
+       if (-e $tmpindex) {
+               unlink $tmpindex or croak $!;
+       }
+       $ENV{GIT_INDEX_FILE} = $tmpindex;
+       index_changes(1);
+       chomp(my $tree = `git-write-tree`);
+       if ($old_index) {
+               $ENV{GIT_INDEX_FILE} = $old_index;
+       } else {
+               delete $ENV{GIT_INDEX_FILE};
+       }
+       if ($tree ne $expected) {
+               croak "Tree mismatch, Got: $tree, Expected: $expected\n";
+       }
+       unlink $tmpindex;
+}
+
+sub parse_diff_tree {
+       my $diff_fh = shift;
+       local $/ = "\0";
+       my $state = 'meta';
+       my @mods;
+       while (<$diff_fh>) {
+               chomp $_; # this gets rid of the trailing "\0"
+               if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
+                                       $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
+                       push @mods, {   mode_a => $1, mode_b => $2,
+                                       sha1_b => $3, chg => $4 };
+                       if ($4 =~ /^(?:C|R)$/) {
+                               $state = 'file_a';
+                       } else {
+                               $state = 'file_b';
+                       }
+               } elsif ($state eq 'file_a') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if ($x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_a} = $_;
+                       $state = 'file_b';
+               } elsif ($state eq 'file_b') {
+                       my $x = $mods[$#mods] or croak "Empty array\n";
+                       if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
+                               croak "Error parsing $_, $x->{chg}\n";
+                       }
+                       $x->{file_b} = $_;
+                       $state = 'meta';
+               } else {
+                       croak "Error parsing $_\n";
+               }
+       }
+       close $diff_fh or croak $!;
+
+       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 svn_checkout_tree {
+       my ($svn_rev, $treeish) = @_;
+       my $from = file_to_s("$REV_DIR/$svn_rev");
+       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 -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 $!;
+       }
+       my $mods = parse_diff_tree($diff_fh);
+       unless (@$mods) {
+               # git can do empty commits, but SVN doesn't allow it...
+               return $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') {
+                       sys(qw(svn rm --force),$m->{file_b});
+                       apply_mod_line_blob($m);
+                       sys(qw(svn add --force), $m->{file_b});
+                       svn_check_prop_executable($m);
+               } elsif ($m->{chg} eq 'A') {
+                       svn_ensure_parent_path( $m->{file_b} );
+                       apply_mod_line_blob($m);
+                       sys(qw(svn add --force), $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;
+}
+
+# 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 svn_commit_tree {
+       my ($svn_rev, $commit) = @_;
+       my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$";
+       my %log_msg = ( msg => '' );
+       open my $msg, '>', $commit_msg or croak $!;
+
+       chomp(my $type = `git-cat-file -t $commit`);
+       if ($type eq 'commit') {
+               my $pid = open my $msg_fh, '-|';
+               defined $pid or croak $!;
+
+               if ($pid == 0) {
+                       exec(qw(git-cat-file commit), $commit) or croak $!;
+               }
+               my $in_msg = 0;
+               while (<$msg_fh>) {
+                       if (!$in_msg) {
+                               $in_msg = 1 if (/^\s*$/);
+                       } elsif (/^git-svn-id: /) {
+                               # skip this, we regenerate the correct one
+                               # on re-fetch anyways
+                       } else {
+                               print $msg $_ or croak $!;
+                       }
+               }
+               close $msg_fh or croak $!;
+       }
+       close $msg or croak $!;
+
+       if ($_edit || ($type eq 'tree')) {
+               my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
+               system($editor, $commit_msg);
+       }
+
+       # file_to_s removes all trailing newlines, so just use chomp() here:
+       open $msg, '<', $commit_msg or croak $!;
+       { local $/; chomp($log_msg{msg} = <$msg>); }
+       close $msg or croak $!;
+
+       my ($oneline) = ($log_msg{msg} =~ /([^\n\r]+)/);
+       print "Committing $commit: $oneline\n";
+
+       my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
+       my ($committed) = grep(/^Committed revision \d+\./,@ci_output);
+       unlink $commit_msg;
+       defined $committed or croak
+                       "Commit output failed to parse committed revision!\n",
+                       join("\n",@ci_output),"\n";
+       my ($rev_committed) = ($committed =~ /^Committed revision (\d+)\./);
+
+       my @svn_up = qw(svn up);
+       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
+       if ($rev_committed == ($svn_rev + 1)) {
+               push @svn_up, "-r$rev_committed";
+               sys(@svn_up);
+               my $info = svn_info('.');
+               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
+               if ($info->{'Last Changed Rev'} != $rev_committed) {
+                       croak "$info->{'Last Changed Rev'} != $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} = $rev_committed;
+               $log_msg{msg} .= "\n";
+               my $parent = file_to_s("$REV_DIR/$svn_rev");
+               git_commit(\%log_msg, $parent, $commit);
+               return $rev_committed;
+       }
+       # resync immediately
+       push @svn_up, "-r$svn_rev";
+       sys(@svn_up);
+       return fetch("$rev_committed=$commit")->{revision};
+}
+
+# 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+)/);
+                       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 eol_cp {
+       my ($from, $to) = @_;
+       my $es = safe_qx(qw/svn propget svn:eol-style/, $to);
+       open my $rfd, '<', $from or croak $!;
+       binmode $rfd or croak $!;
+       open my $wfd, '>', $to or croak $!;
+       binmode $wfd or croak $!;
+
+       my $eol = $EOL{$es} or undef;
+       if ($eol) {
+               print  "$eol: $from => $to\n";
+       }
+       my $buf;
+       while (1) {
+               my ($r, $w, $t);
+               defined($r = sysread($rfd, $buf, 4096)) or croak $!;
+               return unless $r;
+               $buf =~ s/(?:\015|\012|\015\012)/$eol/gs if $eol;
+               for ($w = 0; $w < $r; $w += $t) {
+                       $t = syswrite($wfd, $buf, $r - $w, $w) 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 _ &&
+                               safe_qx(qw/svn propget 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#^/##;
+                       }
+                       unlink $x or croak $!;
+                       eol_cp($tb, $x);
+                       chmod(($mode &~ umask), $x) or croak $!;
+               }
+               print $ui $x,"\0";
+       }
+       close $ui or croak $!;
+}
+
+sub index_changes {
+       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_DIR/$GIT_SVN/info/exclude"],
+                       'add',
+                       $no_text_base);
+}
+
+sub s_to_file {
+       my ($str, $file, $mode) = @_;
+       open my $fd,'>',$file or croak $!;
+       print $fd $str,"\n" or croak $!;
+       close $fd or croak $!;
+       chmod ($mode &~ umask, $file) if (defined $mode);
+}
+
+sub file_to_s {
+       my $file = shift;
+       open my $fd,'<',$file or croak "$!: file: $file\n";
+       local $/;
+       my $ret = <$fd>;
+       close $fd or croak $!;
+       $ret =~ s/\s*$//s;
+       return $ret;
+}
+
+sub assert_revision_unknown {
+       my $revno = shift;
+       if (-f "$REV_DIR/$revno") {
+               croak "$REV_DIR/$revno already exists! ",
+                               "Why are we refetching it?";
+       }
+}
+
+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 assert_revision_eq_or_unknown {
+       my ($revno, $commit) = @_;
+       if (-f "$REV_DIR/$revno") {
+               my $current = file_to_s("$REV_DIR/$revno");
+               if (($commit ne $current) && !trees_eq($commit, $current)) {
+                       croak "$REV_DIR/$revno already exists!\n",
+                               "current: $current\nexpected: $commit\n";
+               }
+               return;
+       }
+}
+
+sub git_commit {
+       my ($log_msg, @parents) = @_;
+       assert_revision_unknown($log_msg->{revision});
+       my $out_fh = IO::File->new_tmpfile or croak $!;
+
+       map_tree_joins() if (@_branch_from && !%tree_map);
+
+       # commit parents can be conditionally bound to a particular
+       # svn revision via: "svn_revno=commit_sha1", filter them out here:
+       my @exec_parents;
+       foreach my $p (@parents) {
+               next unless defined $p;
+               if ($p =~ /^(\d+)=($sha1_short)$/o) {
+                       if ($1 == $log_msg->{revision}) {
+                               push @exec_parents, $2;
+                       }
+               } else {
+                       push @exec_parents, $p if $p =~ /$sha1_short/o;
+               }
+       }
+
+       my $pid = fork;
+       defined $pid or croak $!;
+       if ($pid == 0) {
+               $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
+               index_changes();
+               chomp(my $tree = `git-write-tree`);
+               croak if $?;
+               if (exists $tree_map{$tree}) {
+                       my %seen_parent = map { $_ => 1 } @exec_parents;
+                       foreach (@{$tree_map{$tree}}) {
+                               # MAXPARENT is defined to 16 in commit-tree.c:
+                               if ($seen_parent{$_} || @exec_parents > 16) {
+                                       next;
+                               }
+                               push @exec_parents, $_;
+                               $seen_parent{$_} = 1;
+                       }
+               }
+               my $msg_fh = IO::File->new_tmpfile or croak $!;
+               print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
+                                       "$SVN_URL\@$log_msg->{revision}",
+                                       " $SVN_UUID\n" or croak $!;
+               $msg_fh->flush == 0 or croak $!;
+               seek $msg_fh, 0, 0 or croak $!;
+
+               set_commit_env($log_msg);
+
+               my @exec = ('git-commit-tree',$tree);
+               push @exec, '-p', $_  foreach @exec_parents;
+               open STDIN, '<&', $msg_fh or croak $!;
+               open STDOUT, '>&', $out_fh or croak $!;
+               exec @exec or croak $!;
+       }
+       waitpid($pid,0);
+       croak if $?;
+
+       $out_fh->flush == 0 or croak $!;
+       seek $out_fh, 0, 0 or croak $!;
+       chomp(my $commit = do { local $/; <$out_fh> });
+       if ($commit !~ /^$sha1$/o) {
+               croak "Failed to commit, invalid sha1: $commit\n";
+       }
+       my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+       if (my $primary_parent = shift @exec_parents) {
+               $pid = fork;
+               defined $pid or croak $!;
+               if (!$pid) {
+                       close STDERR;
+                       close STDOUT;
+                       exec 'git-rev-parse','--verify',
+                                               "refs/remotes/$GIT_SVN^0";
+               }
+               waitpid $pid, 0;
+               push @update_ref, $primary_parent unless $?;
+       }
+       sys(@update_ref);
+       sys('git-update-ref',"$GIT_SVN/revs/$log_msg->{revision}",$commit);
+       print "r$log_msg->{revision} = $commit\n";
+       return $commit;
+}
+
+sub set_commit_env {
+       my ($log_msg) = @_;
+       my $author = $log_msg->{author};
+       my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
+                               : ($author,"$author\@$SVN_UUID");
+       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+       $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);
+       }
+       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 {
+       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;
+       }
+
+       # 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 {
+       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 $old;
+       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       if ($@ || !$head) {
+               print STDERR "Please run: $0 rebuild --upgrade\n";
+               exit 1;
+       }
+}
+
+# fills %tree_map with a reverse mapping of trees to commits.  Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+       foreach my $br (@_branch_from) {
+               my $pid = open my $pipe, '-|';
+               defined $pid or croak $!;
+               if ($pid == 0) {
+                       exec(qw(git-rev-list --pretty=raw), $br) or croak $?;
+               }
+               while (<$pipe>) {
+                       if (/^commit ($sha1)$/o) {
+                               my $commit = $1;
+                               my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+                               unless (defined $tree) {
+                                       die "Failed to parse commit $commit\n";
+                               }
+                               push @{$tree_map{$tree}}, $commit;
+                       }
+               }
+               close $pipe or croak $?;
+       }
+}
+
+# '<svn username> = real-name <email address>' mapping based on git-svnimport:
+sub load_authors {
+       open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+       while (<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               my ($user, $name, $email) = ($1, $2, $3);
+               $users{$user} = [$name, $email];
+       }
+       close $authors or croak $!;
+}
+
+__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)
+{
+       msg => 'whitespace-formatted log entry
+',                                             # trailing newline is preserved
+       revision => '8',                        # integer
+       date => '2004-02-24T17:01:44.108345Z',  # commit date
+       author => 'committer name'
+};
+
+
+@mods = array of diff-index line hashes, each element represents one line
+       of diff-index output
+
+diff-index line ($m hash)
+{
+       mode_a => first column of diff-index output, no leading ':',
+       mode_b => second column of diff-index output,
+       sha1_b => sha1sum of the final blob,
+       chg => change type [MCRADT],
+       file_a => original file name of a file (iff chg is 'C' or 'R')
+       file_b => new/current file name of a file (any chg)
+}
+;
diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt
new file mode 100644 (file)
index 0000000..f7d3de4
--- /dev/null
@@ -0,0 +1,319 @@
+git-svn(1)
+==========
+
+NAME
+----
+git-svn - bidirectional operation between a single Subversion branch and git
+
+SYNOPSIS
+--------
+'git-svn' <command> [options] [arguments]
+
+DESCRIPTION
+-----------
+git-svn is a simple conduit for changesets between a single Subversion
+branch and git.
+
+git-svn is not to be confused with git-svnimport.  The were designed
+with very different goals in mind.
+
+git-svn is 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).
+
+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.
+
+COMMANDS
+--------
+init::
+       Creates an empty git repository with additional metadata
+       directories for git-svn.  The Subversion URL must be specified
+       as a command-line argument.
+
+fetch::
+       Fetch unfetched revisions from the Subversion URL we are
+       tracking.  refs/remotes/git-svn will be updated to the
+       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'
+       command (see below) to write git commits back to
+       remotes/git-svn.
+
+       See 'Additional Fetch Arguments' if you are interested in
+       manually joining branches on commit.
+
+commit::
+       Commit specified commit or tree objects to SVN.  This relies on
+       your imported fetch data being up-to-date.  This makes
+       absolutely no attempts to do patching when committing to SVN, it
+       simply overwrites files with those specified in the tree or
+       commit.  All merging is assumed to have taken place
+       independently of git-svn functions.
+
+rebuild::
+       Not a part of daily usage, but this is a useful command if
+       you've just cloned a repository (using git-clone) that was
+       tracked with git-svn.  Unfortunately, git-clone does not clone
+       git-svn metadata and the svn working tree that git-svn uses for
+       its operations.  This rebuilds the metadata so git-svn can
+       resume fetch operations.  A Subversion URL may be optionally
+       specified at the command-line if the directory/repository you're
+       tracking has moved or changed protocols.
+
+show-ignore::
+       Recursively finds and lists the svn:ignore property on
+       directories.  The output is suitable for appending to
+       the $GIT_DIR/info/exclude file.
+
+OPTIONS
+-------
+-r <ARG>::
+--revision <ARG>::
+       Only used with the 'fetch' command.
+
+       Takes any valid -r<argument> svn would accept and passes it
+       directly to svn. -r<ARG1>:<ARG2> ranges and "{" DATE "}" syntax
+       is also supported.  This is passed directly to svn, see svn
+       documentation for more details.
+
+       This can allow you to make partial mirrors when running fetch.
+
+-::
+--stdin::
+       Only used with the 'commit' command.
+
+       Read a list of commits from stdin and commit them in reverse
+       order.  Only the leading sha1 is read from each line, so
+       git-rev-list --pretty=oneline output can be used.
+
+--rmdir::
+       Only used with the 'commit' command.
+
+       Remove directories from the SVN tree if there are no files left
+       behind.  SVN can version empty directories, and they are not
+       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
+
+-e::
+--edit::
+       Only used with the 'commit' command.
+
+       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
+
+-l<num>::
+--find-copies-harder::
+       Both of these are only used with the 'commit' command.
+
+       They are both passed directly to git-diff-tree see
+       git-diff-tree(1) for more information.
+
+       repo-config key: svn.l
+       repo-config key: svn.findcopiesharder
+
+-A<filename>::
+--authors-file=<filename>::
+
+       Syntax is compatible with the files used by git-svnimport and
+       git-cvsimport:
+
+------------------------------------------------------------------------
+loginname = Joe User <user@example.com>
+------------------------------------------------------------------------
+
+       If this option is specified and git-svn encounters an SVN
+       committer name that does not exist in the authors-file, git-svn
+       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.authors-file
+
+ADVANCED OPTIONS
+----------------
+-b<refname>::
+--branch <refname>::
+       Used with 'fetch' or 'commit'.
+
+       This can be used to join arbitrary git branches to remotes/git-svn
+       on new commits where the tree object is equivalent.
+
+       When used with different GIT_SVN_ID values, tags and branches in
+       SVN can be tracked this way, as can some merges where the heads
+       end up having completely equivalent content.  This can even be
+       used to track branches across multiple SVN _repositories_.
+
+       This option may be specified multiple times, once for each
+       branch.
+
+       repo-config key: svn.branch
+
+-i<GIT_SVN_ID>::
+--id <GIT_SVN_ID>::
+       This sets GIT_SVN_ID (instead of using the environment).  See
+       the section on "Tracking Multiple Repositories or Branches" for
+       more information on using GIT_SVN_ID.
+
+COMPATIBILITY OPTIONS
+---------------------
+--upgrade::
+       Only used with the 'rebuild' command.
+
+       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.
+
+       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
+
+Basic Examples
+~~~~~~~~~~~~~~
+
+Tracking and contributing to an Subversion managed-project:
+
+------------------------------------------------------------------------
+# Initialize a tree (like git init-db):
+       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
+# Something is committed to SVN, pull the latest into your branch:
+       git-svn fetch && git pull . remotes/git-svn
+# Append svn:ignore settings to the default git exclude file:
+       git-svn show-ignore >> .git/info/exclude
+------------------------------------------------------------------------
+
+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 the original signal is weak.
+
+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/git-svn directory
+and instead do all of its work in $GIT_DIR/$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.
+
+ADDITIONAL FETCH ARGUMENTS
+--------------------------
+This is for advanced users, most users should ignore this section.
+
+Unfetched SVN revisions may be imported as children of existing commits
+by specifying additional arguments to 'fetch'.  Additional parents may
+optionally be specified in the form of sha1 hex sums at the
+command-line.  Unfetched SVN revisions may also be tied to particular
+git commits with the following syntax:
+
+       svn_revision_number=git_commit_sha1
+
+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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+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.
+
+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`
+------------------------------------------------------------------------
+
+BUGS
+----
+If 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
+working trees with metadata files.
+
+svn:keywords can't be ignored in Subversion (at least I don't know of
+a way to ignore them).
+
+Renamed and copied directories are not detected by git and hence not
+tracked when committing to SVN.  I do not plan on adding support for
+this as it's quite difficult and time-consuming to get working for all
+the possible corner cases (git doesn't do it, either).  Renamed and
+copied files are fully supported if they're similar enough for git to
+detect them.
+
+Author
+------
+Written by Eric Wong <normalperson@yhbt.net>.
+
+Documentation
+-------------
+Written by Eric Wong <normalperson@yhbt.net>.
diff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh
new file mode 100644 (file)
index 0000000..a98e9d1
--- /dev/null
@@ -0,0 +1,39 @@
+PATH=$PWD/../:$PATH
+if test -d ../../../t
+then
+    cd ../../../t
+else
+    echo "Must be run in contrib/git-svn/t" >&2
+    exit 1
+fi
+
+. ./test-lib.sh
+
+GIT_DIR=$PWD/.git
+GIT_SVN_DIR=$GIT_DIR/git-svn
+SVN_TREE=$GIT_SVN_DIR/tree
+
+svnadmin >/dev/null 2>&1
+if test $? != 1
+then
+    test_expect_success 'skipping contrib/git-svn test' :
+    test_done
+    exit
+fi
+
+svn >/dev/null 2>&1
+if test $? != 1
+then
+    test_expect_success 'skipping contrib/git-svn test' :
+    test_done
+    exit
+fi
+
+svnrepo=$PWD/svnrepo
+
+set -e
+
+svnadmin create $svnrepo
+svnrepo="file://$svnrepo/test-git-svn"
+
+
diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh
new file mode 100644 (file)
index 0000000..8b3a0d9
--- /dev/null
@@ -0,0 +1,177 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn tests'
+. ./lib-git-svn.sh
+
+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 -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"
+
+test_expect_success \
+    'import an SVN revision into git' \
+    'git-svn fetch'
+
+
+name='try a deep --rmdir with a commit'
+git checkout -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 &&
+     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_code 1 "$name" \
+    'git-svn commit --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 -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_code 1 "$name" \
+    'git-svn commit --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 -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_code 1 "$name" \
+    'git-svn commit --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 -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_code 1 "$name" \
+    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
+    || true
+
+
+name='remove executable bit from a file'
+rm -f $GIT_DIR/index
+git checkout -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 &&
+     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 &&
+     test -x $SVN_TREE/exec.sh"
+
+
+
+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 &&
+     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 &&
+     test -x $SVN_TREE/bar/zzz &&
+     test -L $SVN_TREE/exec-2.sh"
+
+
+
+name='modify a symlink to become a file'
+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 &&
+     test -f $SVN_TREE/exec-2.sh &&
+     test ! -L $SVN_TREE/exec-2.sh &&
+     diff -u help $SVN_TREE/exec-2.sh"
+
+
+
+name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
+GIT_SVN_ID=alt
+export GIT_SVN_ID
+test_expect_success "$name" \
+    "git-svn init $svnrepo && git-svn fetch &&
+     git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+     git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
+     diff -u a b"
+
+test_done
+
diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/contrib/git-svn/t/t0001-contrib-git-svn-props.sh
new file mode 100644 (file)
index 0000000..23a5a2a
--- /dev/null
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Eric Wong
+#
+
+test_description='git-svn property tests'
+. ./lib-git-svn.sh
+
+mkdir import
+
+a_crlf=
+a_lf=
+a_cr=
+a_ne_crlf=
+a_ne_lf=
+a_ne_cr=
+a_empty=
+a_empty_lf=
+a_empty_cr=
+a_empty_crlf=
+
+cd import
+       cat >> kw.c <<\EOF
+/* Make it look like somebody copied a file from CVS into SVN: */
+/* $Id: kw.c,v 1.1.1.1 1994/03/06 00:00:00 eric Exp $ */
+EOF
+
+       printf "Hello\r\nWorld\r\n" > crlf
+       a_crlf=`git-hash-object -w crlf`
+       printf "Hello\rWorld\r" > cr
+       a_cr=`git-hash-object -w cr`
+       printf "Hello\nWorld\n" > lf
+       a_lf=`git-hash-object -w lf`
+
+       printf "Hello\r\nWorld" > ne_crlf
+       a_ne_crlf=`git-hash-object -w ne_crlf`
+       printf "Hello\nWorld" > ne_lf
+       a_ne_lf=`git-hash-object -w ne_lf`
+       printf "Hello\rWorld" > ne_cr
+       a_ne_cr=`git-hash-object -w ne_cr`
+
+       touch empty
+       a_empty=`git-hash-object -w empty`
+       printf "\n" > empty_lf
+       a_empty_lf=`git-hash-object -w empty_lf`
+       printf "\r" > empty_cr
+       a_empty_cr=`git-hash-object -w empty_cr`
+       printf "\r\n" > empty_crlf
+       a_empty_crlf=`git-hash-object -w empty_crlf`
+
+       svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
+cd ..
+
+rm -rf import
+svn co "$svnrepo" test_wc
+
+cd test_wc
+       echo 'Greetings' >> kw.c
+       svn commit -m 'Not yet an $Id$'
+       svn up
+
+       echo 'Hello world' >> kw.c
+       svn commit -m 'Modified file, but still not yet an $Id$'
+       svn up
+
+       svn propset svn:keywords Id kw.c
+       svn commit -m 'Propset $Id$'
+       svn up
+cd ..
+
+git-svn init "$svnrepo"
+git-svn fetch
+
+git checkout -b mybranch remotes/git-svn
+echo 'Hi again' >> kw.c
+name='test svn:keywords ignoring'
+
+git commit -a -m "$name"
+git-svn commit remotes/git-svn..mybranch
+git pull . remotes/git-svn
+
+expect='/* $Id$ */'
+got="`sed -ne 2p kw.c`"
+test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'"
+
+cd test_wc
+       svn propset svn:eol-style CR empty
+       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 ..
+
+git-svn fetch
+git pull . remotes/git-svn
+
+svn co "$svnrepo" new_wc
+for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf
+do
+       test_expect_success "Comparing $i" "cmp $i new_wc/$i"
+done
+
+
+cd test_wc
+       printf '$Id$\rHello\rWorld\r' > cr
+       printf '$Id$\rHello\rWorld' > ne_cr
+       a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git-hash-object --stdin`
+       a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git-hash-object --stdin`
+       svn propset svn:eol-style CRLF cr
+       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
+cd ..
+
+git-svn fetch
+git pull . remotes/git-svn
+
+b_cr="`git-hash-object cr`"
+b_ne_cr="`git-hash-object ne_cr`"
+
+test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'"
+test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'"
+
+test_done
diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview
new file mode 100755 (executable)
index 0000000..3b6bdce
--- /dev/null
@@ -0,0 +1,1029 @@
+#! /usr/bin/env python
+
+# 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.
+
+""" gitview
+GUI browser for git repository
+This program is based on bzrk by Scott James Remnant <scott@ubuntu.com>
+"""
+__copyright__ = "Copyright (C) 2006 Hewlett-Packard Development Company, L.P."
+__author__    = "Aneesh Kumar K.V <aneesh.kumar@hp.com>"
+
+
+import sys
+import os
+import gtk
+import pygtk
+import pango
+import re
+import time
+import gobject
+import cairo
+import math
+import string
+
+try:
+    import gtksourceview
+    have_gtksourceview = True
+except ImportError:
+    have_gtksourceview = False
+    print "Running without gtksourceview module"
+
+re_ident = re.compile('(author|committer) (?P<ident>.*) (?P<epoch>\d+) (?P<tz>[+-]\d{4})')
+
+def list_to_string(args, skip):
+       count = len(args)
+       i = skip
+       str_arg=" "
+       while (i < count ):
+               str_arg = str_arg + args[i]
+               str_arg = str_arg + " "
+               i = i+1
+
+       return str_arg
+
+def show_date(epoch, tz):
+       secs = float(epoch)
+       tzsecs = float(tz[1:3]) * 3600
+       tzsecs += float(tz[3:5]) * 60
+       if (tz[0] == "+"):
+               secs += tzsecs
+       else:
+               secs -= tzsecs
+
+       return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs))
+
+
+class CellRendererGraph(gtk.GenericCellRenderer):
+       """Cell renderer for directed graph.
+
+       This module contains the implementation of a custom GtkCellRenderer that
+       draws part of the directed graph based on the lines suggested by the code
+       in graph.py.
+
+       Because we're shiny, we use Cairo to do this, and because we're naughty
+       we cheat and draw over the bits of the TreeViewColumn that are supposed to
+       just be for the background.
+
+       Properties:
+       node              (column, colour, [ names ]) tuple to draw revision node,
+       in_lines          (start, end, colour) tuple list to draw inward lines,
+       out_lines         (start, end, colour) tuple list to draw outward lines.
+       """
+
+       __gproperties__ = {
+       "node":         ( gobject.TYPE_PYOBJECT, "node",
+                         "revision node instruction",
+                         gobject.PARAM_WRITABLE
+                       ),
+       "in-lines":     ( gobject.TYPE_PYOBJECT, "in-lines",
+                         "instructions to draw lines into the cell",
+                         gobject.PARAM_WRITABLE
+                       ),
+       "out-lines":    ( gobject.TYPE_PYOBJECT, "out-lines",
+                         "instructions to draw lines out of the cell",
+                         gobject.PARAM_WRITABLE
+                       ),
+       }
+
+       def do_set_property(self, property, value):
+               """Set properties from GObject properties."""
+               if property.name == "node":
+                       self.node = value
+               elif property.name == "in-lines":
+                       self.in_lines = value
+               elif property.name == "out-lines":
+                       self.out_lines = value
+               else:
+                       raise AttributeError, "no such property: '%s'" % property.name
+
+       def box_size(self, widget):
+               """Calculate box size based on widget's font.
+
+               Cache this as it's probably expensive to get.  It ensures that we
+               draw the graph at least as large as the text.
+               """
+               try:
+                       return self._box_size
+               except AttributeError:
+                       pango_ctx = widget.get_pango_context()
+                       font_desc = widget.get_style().font_desc
+                       metrics = pango_ctx.get_metrics(font_desc)
+
+                       ascent = pango.PIXELS(metrics.get_ascent())
+                       descent = pango.PIXELS(metrics.get_descent())
+
+                       self._box_size = ascent + descent + 6
+                       return self._box_size
+
+       def set_colour(self, ctx, colour, bg, fg):
+               """Set the context source colour.
+
+               Picks a distinct colour based on an internal wheel; the bg
+               parameter provides the value that should be assigned to the 'zero'
+               colours and the fg parameter provides the multiplier that should be
+               applied to the foreground colours.
+               """
+               colours = [
+                   ( 1.0, 0.0, 0.0 ),
+                   ( 1.0, 1.0, 0.0 ),
+                   ( 0.0, 1.0, 0.0 ),
+                   ( 0.0, 1.0, 1.0 ),
+                   ( 0.0, 0.0, 1.0 ),
+                   ( 1.0, 0.0, 1.0 ),
+                   ]
+
+               colour %= len(colours)
+               red   = (colours[colour][0] * fg) or bg
+               green = (colours[colour][1] * fg) or bg
+               blue  = (colours[colour][2] * fg) or bg
+
+               ctx.set_source_rgb(red, green, blue)
+
+       def on_get_size(self, widget, cell_area):
+               """Return the size we need for this cell.
+
+               Each cell is drawn individually and is only as wide as it needs
+               to be, we let the TreeViewColumn take care of making them all
+               line up.
+               """
+               box_size = self.box_size(widget)
+
+               cols = self.node[0]
+               for start, end, colour in self.in_lines + self.out_lines:
+                       cols = int(max(cols, start, end))
+
+               (column, colour, names) = self.node
+               names_len = 0
+               if (len(names) != 0):
+                       for item in names:
+                               names_len += len(item)
+
+               width = box_size * (cols + 1 ) + names_len
+               height = box_size
+
+               # FIXME I have no idea how to use cell_area properly
+               return (0, 0, width, height)
+
+       def on_render(self, window, widget, bg_area, cell_area, exp_area, flags):
+               """Render an individual cell.
+
+               Draws the cell contents using cairo, taking care to clip what we
+               do to within the background area so we don't draw over other cells.
+               Note that we're a bit naughty there and should really be drawing
+               in the cell_area (or even the exposed area), but we explicitly don't
+               want any gutter.
+
+               We try and be a little clever, if the line we need to draw is going
+               to cross other columns we actually draw it as in the .---' style
+               instead of a pure diagonal ... this reduces confusion by an
+               incredible amount.
+               """
+               ctx = window.cairo_create()
+               ctx.rectangle(bg_area.x, bg_area.y, bg_area.width, bg_area.height)
+               ctx.clip()
+
+               box_size = self.box_size(widget)
+
+               ctx.set_line_width(box_size / 8)
+               ctx.set_line_cap(cairo.LINE_CAP_SQUARE)
+
+               # Draw lines into the cell
+               for start, end, colour in self.in_lines:
+                       ctx.move_to(cell_area.x + box_size * start + box_size / 2,
+                                       bg_area.y - bg_area.height / 2)
+
+                       if start - end > 1:
+                               ctx.line_to(cell_area.x + box_size * start, bg_area.y)
+                               ctx.line_to(cell_area.x + box_size * end + box_size, bg_area.y)
+                       elif start - end < -1:
+                               ctx.line_to(cell_area.x + box_size * start + box_size,
+                                               bg_area.y)
+                               ctx.line_to(cell_area.x + box_size * end, bg_area.y)
+
+                       ctx.line_to(cell_area.x + box_size * end + box_size / 2,
+                                       bg_area.y + bg_area.height / 2)
+
+                       self.set_colour(ctx, colour, 0.0, 0.65)
+                       ctx.stroke()
+
+               # Draw lines out of the cell
+               for start, end, colour in self.out_lines:
+                       ctx.move_to(cell_area.x + box_size * start + box_size / 2,
+                                       bg_area.y + bg_area.height / 2)
+
+                       if start - end > 1:
+                               ctx.line_to(cell_area.x + box_size * start,
+                                               bg_area.y + bg_area.height)
+                               ctx.line_to(cell_area.x + box_size * end + box_size,
+                                               bg_area.y + bg_area.height)
+                       elif start - end < -1:
+                               ctx.line_to(cell_area.x + box_size * start + box_size,
+                                               bg_area.y + bg_area.height)
+                               ctx.line_to(cell_area.x + box_size * end,
+                                               bg_area.y + bg_area.height)
+
+                       ctx.line_to(cell_area.x + box_size * end + box_size / 2,
+                                       bg_area.y + bg_area.height / 2 + bg_area.height)
+
+                       self.set_colour(ctx, colour, 0.0, 0.65)
+                       ctx.stroke()
+
+               # Draw the revision node in the right column
+               (column, colour, names) = self.node
+               ctx.arc(cell_area.x + box_size * column + box_size / 2,
+                               cell_area.y + cell_area.height / 2,
+                               box_size / 4, 0, 2 * math.pi)
+
+
+               self.set_colour(ctx, colour, 0.0, 0.5)
+               ctx.stroke_preserve()
+
+               self.set_colour(ctx, colour, 0.5, 1.0)
+               ctx.fill_preserve()
+
+               if (len(names) != 0):
+                       name = " "
+                       for item in names:
+                               name = name + item + " "
+
+                       ctx.set_font_size(13)
+                       if (flags & 1):
+                               self.set_colour(ctx, colour, 0.5, 1.0)
+                       else:
+                               self.set_colour(ctx, colour, 0.0, 0.5)
+                       ctx.show_text(name)
+
+class Commit:
+       """ This represent a commit object obtained after parsing the git-rev-list
+       output """
+
+       children_sha1 = {}
+
+       def __init__(self, commit_lines):
+               self.message            = ""
+               self.author             = ""
+               self.date               = ""
+               self.committer          = ""
+               self.commit_date        = ""
+               self.commit_sha1        = ""
+               self.parent_sha1        = [ ]
+               self.parse_commit(commit_lines)
+
+
+       def parse_commit(self, commit_lines):
+
+               # First line is the sha1 lines
+               line = string.strip(commit_lines[0])
+               sha1 = re.split(" ", line)
+               self.commit_sha1 = sha1[0]
+               self.parent_sha1 = sha1[1:]
+
+               #build the child list
+               for parent_id in self.parent_sha1:
+                       try:
+                               Commit.children_sha1[parent_id].append(self.commit_sha1)
+                       except KeyError:
+                               Commit.children_sha1[parent_id] = [self.commit_sha1]
+
+               # IF we don't have parent
+               if (len(self.parent_sha1) == 0):
+                       self.parent_sha1 = [0]
+
+               for line in commit_lines[1:]:
+                       m = re.match("^ ", line)
+                       if (m != None):
+                               # First line of the commit message used for short log
+                               if self.message == "":
+                                       self.message = string.strip(line)
+                               continue
+
+                       m = re.match("tree", line)
+                       if (m != None):
+                               continue
+
+                       m = re.match("parent", line)
+                       if (m != None):
+                               continue
+
+                       m = re_ident.match(line)
+                       if (m != None):
+                               date = show_date(m.group('epoch'), m.group('tz'))
+                               if m.group(1) == "author":
+                                       self.author = m.group('ident')
+                                       self.date = date
+                               elif m.group(1) == "committer":
+                                       self.committer = m.group('ident')
+                                       self.commit_date = date
+
+                               continue
+
+       def get_message(self, with_diff=0):
+               if (with_diff == 1):
+                       message = self.diff_tree()
+               else:
+                       fp = os.popen("git cat-file commit " + self.commit_sha1)
+                       message = fp.read()
+                       fp.close()
+
+               return message
+
+       def diff_tree(self):
+               fp = os.popen("git diff-tree --pretty --cc  -v -p --always " +  self.commit_sha1)
+               diff = fp.read()
+               fp.close()
+               return diff
+
+class DiffWindow:
+       """Diff window.
+       This object represents and manages a single window containing the
+       differences between two revisions on a branch.
+       """
+
+       def __init__(self):
+               self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+               self.window.set_border_width(0)
+               self.window.set_title("Git repository browser diff window")
+
+               # Use two thirds of the screen by default
+               screen = self.window.get_screen()
+               monitor = screen.get_monitor_geometry(0)
+               width = int(monitor.width * 0.66)
+               height = int(monitor.height * 0.66)
+               self.window.set_default_size(width, height)
+
+               self.construct()
+
+       def construct(self):
+               """Construct the window contents."""
+               vbox = gtk.VBox()
+               self.window.add(vbox)
+               vbox.show()
+
+               menu_bar = gtk.MenuBar()
+               save_menu = gtk.ImageMenuItem(gtk.STOCK_SAVE)
+               save_menu.connect("activate", self.save_menu_response, "save")
+               save_menu.show()
+               menu_bar.append(save_menu)
+               vbox.pack_start(menu_bar, expand=False, fill=True)
+               menu_bar.show()
+
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               vbox.pack_start(scrollwin, expand=True, fill=True)
+               scrollwin.show()
+
+               if have_gtksourceview:
+                       self.buffer = gtksourceview.SourceBuffer()
+                       slm = gtksourceview.SourceLanguagesManager()
+                       gsl = slm.get_language_from_mime_type("text/x-patch")
+                       self.buffer.set_highlight(True)
+                       self.buffer.set_language(gsl)
+                       sourceview = gtksourceview.SourceView(self.buffer)
+               else:
+                       self.buffer = gtk.TextBuffer()
+                       sourceview = gtk.TextView(self.buffer)
+
+               sourceview.set_editable(False)
+               sourceview.modify_font(pango.FontDescription("Monospace"))
+               scrollwin.add(sourceview)
+               sourceview.show()
+
+
+       def set_diff(self, commit_sha1, parent_sha1, encoding):
+               """Set the differences showed by this window.
+               Compares the two trees and populates the window with the
+               differences.
+               """
+               # Diff with the first commit or the last commit shows nothing
+               if (commit_sha1 == 0 or parent_sha1 == 0 ):
+                       return
+
+               fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1)
+               self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8'))
+               fp.close()
+               self.window.show()
+
+       def save_menu_response(self, widget, string):
+               dialog = gtk.FileChooserDialog("Save..", None, gtk.FILE_CHOOSER_ACTION_SAVE,
+                               (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                                       gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+               dialog.set_default_response(gtk.RESPONSE_OK)
+               response = dialog.run()
+               if response == gtk.RESPONSE_OK:
+                       patch_buffer = self.buffer.get_text(self.buffer.get_start_iter(),
+                                       self.buffer.get_end_iter())
+                       fp = open(dialog.get_filename(), "w")
+                       fp.write(patch_buffer)
+                       fp.close()
+               dialog.destroy()
+
+class GitView:
+       """ This is the main class
+       """
+       version = "0.8"
+
+       def __init__(self, with_diff=0):
+               self.with_diff = with_diff
+               self.window =   gtk.Window(gtk.WINDOW_TOPLEVEL)
+               self.window.set_border_width(0)
+               self.window.set_title("Git repository browser")
+
+               self.get_encoding()
+               self.get_bt_sha1()
+
+               # Use three-quarters of the screen by default
+               screen = self.window.get_screen()
+               monitor = screen.get_monitor_geometry(0)
+               width = int(monitor.width * 0.75)
+               height = int(monitor.height * 0.75)
+               self.window.set_default_size(width, height)
+
+               # FIXME AndyFitz!
+               icon = self.window.render_icon(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON)
+               self.window.set_icon(icon)
+
+               self.accel_group = gtk.AccelGroup()
+               self.window.add_accel_group(self.accel_group)
+               self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh);
+               self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize);
+               self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen);
+               self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen);
+
+               self.window.add(self.construct())
+
+       def refresh(self, widget, event=None, *arguments, **keywords):
+               self.get_encoding()
+               self.get_bt_sha1()
+               Commit.children_sha1 = {}
+               self.set_branch(sys.argv[without_diff:])
+               self.window.show()
+               return True
+
+       def maximize(self, widget, event=None, *arguments, **keywords):
+               self.window.maximize()
+               return True
+
+       def fullscreen(self, widget, event=None, *arguments, **keywords):
+               self.window.fullscreen()
+               return True
+
+       def unfullscreen(self, widget, event=None, *arguments, **keywords):
+               self.window.unfullscreen()
+               return True
+
+       def get_bt_sha1(self):
+               """ Update the bt_sha1 dictionary with the
+               respective sha1 details """
+
+               self.bt_sha1 = { }
+               ls_remote = re.compile('^(.{40})\trefs/([^^]+)(?:\\^(..))?$');
+               fp = os.popen('git ls-remote "${GIT_DIR-.git}"')
+               while 1:
+                       line = string.strip(fp.readline())
+                       if line == '':
+                               break
+                       m = ls_remote.match(line)
+                       if not m:
+                               continue
+                       (sha1, name) = (m.group(1), m.group(2))
+                       if not self.bt_sha1.has_key(sha1):
+                               self.bt_sha1[sha1] = []
+                       self.bt_sha1[sha1].append(name)
+               fp.close()
+
+       def get_encoding(self):
+               fp = os.popen("git repo-config --get i18n.commitencoding")
+               self.encoding=string.strip(fp.readline())
+               fp.close()
+               if (self.encoding == ""):
+                       self.encoding = "utf-8"
+
+
+       def construct(self):
+               """Construct the window contents."""
+               vbox = gtk.VBox()
+               paned = gtk.VPaned()
+               paned.pack1(self.construct_top(), resize=False, shrink=True)
+               paned.pack2(self.construct_bottom(), resize=False, shrink=True)
+               menu_bar = gtk.MenuBar()
+               menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL)
+               help_menu = gtk.MenuItem("Help")
+               menu = gtk.Menu()
+               about_menu = gtk.MenuItem("About")
+               menu.append(about_menu)
+               about_menu.connect("activate", self.about_menu_response, "about")
+               about_menu.show()
+               help_menu.set_submenu(menu)
+               help_menu.show()
+               menu_bar.append(help_menu)
+               menu_bar.show()
+               vbox.pack_start(menu_bar, expand=False, fill=True)
+               vbox.pack_start(paned, expand=True, fill=True)
+               paned.show()
+               vbox.show()
+               return vbox
+
+
+       def construct_top(self):
+               """Construct the top-half of the window."""
+               vbox = gtk.VBox(spacing=6)
+               vbox.set_border_width(12)
+               vbox.show()
+
+
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               vbox.pack_start(scrollwin, expand=True, fill=True)
+               scrollwin.show()
+
+               self.treeview = gtk.TreeView()
+               self.treeview.set_rules_hint(True)
+               self.treeview.set_search_column(4)
+               self.treeview.connect("cursor-changed", self._treeview_cursor_cb)
+               scrollwin.add(self.treeview)
+               self.treeview.show()
+
+               cell = CellRendererGraph()
+               column = gtk.TreeViewColumn()
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "node", 1)
+               column.add_attribute(cell, "in-lines", 2)
+               column.add_attribute(cell, "out-lines", 3)
+               self.treeview.append_column(column)
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 65)
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("Message")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 4)
+               self.treeview.append_column(column)
+
+               cell = gtk.CellRendererText()
+               cell.set_property("width-chars", 40)
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("Author")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 5)
+               self.treeview.append_column(column)
+
+               cell = gtk.CellRendererText()
+               cell.set_property("ellipsize", pango.ELLIPSIZE_END)
+               column = gtk.TreeViewColumn("Date")
+               column.set_resizable(True)
+               column.pack_start(cell, expand=True)
+               column.add_attribute(cell, "text", 6)
+               self.treeview.append_column(column)
+
+               return vbox
+
+       def about_menu_response(self, widget, string):
+               dialog = gtk.AboutDialog()
+               dialog.set_name("Gitview")
+               dialog.set_version(GitView.version)
+               dialog.set_authors(["Aneesh Kumar K.V <aneesh.kumar@hp.com>"])
+               dialog.set_website("http://www.kernel.org/pub/software/scm/git/")
+               dialog.set_copyright("Use and distribute under the terms of the GNU General Public License")
+               dialog.set_wrap_license(True)
+               dialog.run()
+               dialog.destroy()
+
+
+       def construct_bottom(self):
+               """Construct the bottom half of the window."""
+               vbox = gtk.VBox(False, spacing=6)
+               vbox.set_border_width(12)
+               (width, height) = self.window.get_size()
+               vbox.set_size_request(width, int(height / 2.5))
+               vbox.show()
+
+               self.table = gtk.Table(rows=4, columns=4)
+               self.table.set_row_spacings(6)
+               self.table.set_col_spacings(6)
+               vbox.pack_start(self.table, expand=False, fill=True)
+               self.table.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               label = gtk.Label()
+               label.set_markup("<b>Revision:</b>")
+               align.add(label)
+               self.table.attach(align, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
+               label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               self.revid_label = gtk.Label()
+               self.revid_label.set_selectable(True)
+               align.add(self.revid_label)
+               self.table.attach(align, 1, 2, 0, 1, gtk.EXPAND | gtk.FILL, gtk.FILL)
+               self.revid_label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               label = gtk.Label()
+               label.set_markup("<b>Committer:</b>")
+               align.add(label)
+               self.table.attach(align, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
+               label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               self.committer_label = gtk.Label()
+               self.committer_label.set_selectable(True)
+               align.add(self.committer_label)
+               self.table.attach(align, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, gtk.FILL)
+               self.committer_label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               label = gtk.Label()
+               label.set_markup("<b>Timestamp:</b>")
+               align.add(label)
+               self.table.attach(align, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
+               label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               self.timestamp_label = gtk.Label()
+               self.timestamp_label.set_selectable(True)
+               align.add(self.timestamp_label)
+               self.table.attach(align, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, gtk.FILL)
+               self.timestamp_label.show()
+               align.show()
+
+               align = gtk.Alignment(0.0, 0.5)
+               label = gtk.Label()
+               label.set_markup("<b>Parents:</b>")
+               align.add(label)
+               self.table.attach(align, 0, 1, 3, 4, gtk.FILL, gtk.FILL)
+               label.show()
+               align.show()
+               self.parents_widgets = []
+
+               align = gtk.Alignment(0.0, 0.5)
+               label = gtk.Label()
+               label.set_markup("<b>Children:</b>")
+               align.add(label)
+               self.table.attach(align, 2, 3, 3, 4, gtk.FILL, gtk.FILL)
+               label.show()
+               align.show()
+               self.children_widgets = []
+
+               scrollwin = gtk.ScrolledWindow()
+               scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               scrollwin.set_shadow_type(gtk.SHADOW_IN)
+               vbox.pack_start(scrollwin, expand=True, fill=True)
+               scrollwin.show()
+
+               if have_gtksourceview:
+                       self.message_buffer = gtksourceview.SourceBuffer()
+                       slm = gtksourceview.SourceLanguagesManager()
+                       gsl = slm.get_language_from_mime_type("text/x-patch")
+                       self.message_buffer.set_highlight(True)
+                       self.message_buffer.set_language(gsl)
+                       sourceview = gtksourceview.SourceView(self.message_buffer)
+               else:
+                       self.message_buffer = gtk.TextBuffer()
+                       sourceview = gtk.TextView(self.message_buffer)
+
+               sourceview.set_editable(False)
+               sourceview.modify_font(pango.FontDescription("Monospace"))
+               scrollwin.add(sourceview)
+               sourceview.show()
+
+               return vbox
+
+       def _treeview_cursor_cb(self, *args):
+               """Callback for when the treeview cursor changes."""
+               (path, col) = self.treeview.get_cursor()
+               commit = self.model[path][0]
+
+               if commit.committer is not None:
+                       committer = commit.committer
+                       timestamp = commit.commit_date
+                       message   =  commit.get_message(self.with_diff)
+                       revid_label = commit.commit_sha1
+               else:
+                       committer = ""
+                       timestamp = ""
+                       message = ""
+                       revid_label = ""
+
+               self.revid_label.set_text(revid_label)
+               self.committer_label.set_text(committer)
+               self.timestamp_label.set_text(timestamp)
+               self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8'))
+
+               for widget in self.parents_widgets:
+                       self.table.remove(widget)
+
+               self.parents_widgets = []
+               self.table.resize(4 + len(commit.parent_sha1) - 1, 4)
+               for idx, parent_id in enumerate(commit.parent_sha1):
+                       self.table.set_row_spacing(idx + 3, 0)
+
+                       align = gtk.Alignment(0.0, 0.0)
+                       self.parents_widgets.append(align)
+                       self.table.attach(align, 1, 2, idx + 3, idx + 4,
+                                       gtk.EXPAND | gtk.FILL, gtk.FILL)
+                       align.show()
+
+                       hbox = gtk.HBox(False, 0)
+                       align.add(hbox)
+                       hbox.show()
+
+                       label = gtk.Label(parent_id)
+                       label.set_selectable(True)
+                       hbox.pack_start(label, expand=False, fill=True)
+                       label.show()
+
+                       image = gtk.Image()
+                       image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
+                       image.show()
+
+                       button = gtk.Button()
+                       button.add(image)
+                       button.set_relief(gtk.RELIEF_NONE)
+                       button.connect("clicked", self._go_clicked_cb, parent_id)
+                       hbox.pack_start(button, expand=False, fill=True)
+                       button.show()
+
+                       image = gtk.Image()
+                       image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
+                       image.show()
+
+                       button = gtk.Button()
+                       button.add(image)
+                       button.set_relief(gtk.RELIEF_NONE)
+                       button.set_sensitive(True)
+                       button.connect("clicked", self._show_clicked_cb,
+                                       commit.commit_sha1, parent_id, self.encoding)
+                       hbox.pack_start(button, expand=False, fill=True)
+                       button.show()
+
+               # Populate with child details
+               for widget in self.children_widgets:
+                       self.table.remove(widget)
+
+               self.children_widgets = []
+               try:
+                       child_sha1 = Commit.children_sha1[commit.commit_sha1]
+               except KeyError:
+                       # We don't have child
+                       child_sha1 = [ 0 ]
+
+               if ( len(child_sha1) > len(commit.parent_sha1)):
+                       self.table.resize(4 + len(child_sha1) - 1, 4)
+
+               for idx, child_id in enumerate(child_sha1):
+                       self.table.set_row_spacing(idx + 3, 0)
+
+                       align = gtk.Alignment(0.0, 0.0)
+                       self.children_widgets.append(align)
+                       self.table.attach(align, 3, 4, idx + 3, idx + 4,
+                                       gtk.EXPAND | gtk.FILL, gtk.FILL)
+                       align.show()
+
+                       hbox = gtk.HBox(False, 0)
+                       align.add(hbox)
+                       hbox.show()
+
+                       label = gtk.Label(child_id)
+                       label.set_selectable(True)
+                       hbox.pack_start(label, expand=False, fill=True)
+                       label.show()
+
+                       image = gtk.Image()
+                       image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU)
+                       image.show()
+
+                       button = gtk.Button()
+                       button.add(image)
+                       button.set_relief(gtk.RELIEF_NONE)
+                       button.connect("clicked", self._go_clicked_cb, child_id)
+                       hbox.pack_start(button, expand=False, fill=True)
+                       button.show()
+
+                       image = gtk.Image()
+                       image.set_from_stock(gtk.STOCK_FIND, gtk.ICON_SIZE_MENU)
+                       image.show()
+
+                       button = gtk.Button()
+                       button.add(image)
+                       button.set_relief(gtk.RELIEF_NONE)
+                       button.set_sensitive(True)
+                       button.connect("clicked", self._show_clicked_cb,
+                                       child_id, commit.commit_sha1, self.encoding)
+                       hbox.pack_start(button, expand=False, fill=True)
+                       button.show()
+
+       def _destroy_cb(self, widget):
+               """Callback for when a window we manage is destroyed."""
+               self.quit()
+
+
+       def quit(self):
+               """Stop the GTK+ main loop."""
+               gtk.main_quit()
+
+       def run(self, args):
+               self.set_branch(args)
+               self.window.connect("destroy", self._destroy_cb)
+               self.window.show()
+               gtk.main()
+
+       def set_branch(self, args):
+               """Fill in different windows with info from the reposiroty"""
+               fp = os.popen("git rev-parse --sq --default HEAD " + list_to_string(args, 1))
+               git_rev_list_cmd = fp.read()
+               fp.close()
+               fp = os.popen("git rev-list  --header --topo-order --parents " + git_rev_list_cmd)
+               self.update_window(fp)
+
+       def update_window(self, fp):
+               commit_lines = []
+
+               self.model = gtk.ListStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+                               gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str, str, str)
+
+               # used for cursor positioning
+               self.index = {}
+
+               self.colours = {}
+               self.nodepos = {}
+               self.incomplete_line = {}
+               self.commits = []
+
+               index = 0
+               last_colour = 0
+               last_nodepos = -1
+               out_line = []
+               input_line = fp.readline()
+               while (input_line != ""):
+                       # The commit header ends with '\0'
+                       # This NULL is immediately followed by the sha1 of the
+                       # next commit
+                       if (input_line[0] != '\0'):
+                               commit_lines.append(input_line)
+                               input_line = fp.readline()
+                               continue;
+
+                       commit = Commit(commit_lines)
+                       if (commit != None ):
+                               self.commits.append(commit)
+
+                       # Skip the '\0
+                       commit_lines = []
+                       commit_lines.append(input_line[1:])
+                       input_line = fp.readline()
+
+               fp.close()
+
+               for commit in self.commits:
+                       (out_line, last_colour, last_nodepos) = self.draw_graph(commit,
+                                                                               index, out_line,
+                                                                               last_colour,
+                                                                               last_nodepos)
+                       self.index[commit.commit_sha1] = index
+                       index += 1
+
+               self.treeview.set_model(self.model)
+               self.treeview.show()
+
+       def draw_graph(self, commit, index, out_line, last_colour, last_nodepos):
+               in_line=[]
+
+               #   |   -> outline
+               #   X
+               #   |\  <- inline
+
+               # Reset nodepostion
+               if (last_nodepos > 5):
+                       last_nodepos = -1
+
+               # Add the incomplete lines of the last cell in this
+               try:
+                       colour = self.colours[commit.commit_sha1]
+               except KeyError:
+                       self.colours[commit.commit_sha1] = last_colour+1
+                       last_colour = self.colours[commit.commit_sha1]
+                       colour =   self.colours[commit.commit_sha1]
+
+               try:
+                       node_pos = self.nodepos[commit.commit_sha1]
+               except KeyError:
+                       self.nodepos[commit.commit_sha1] = last_nodepos+1
+                       last_nodepos = self.nodepos[commit.commit_sha1]
+                       node_pos =  self.nodepos[commit.commit_sha1]
+
+               #The first parent always continue on the same line
+               try:
+                       # check we alreay have the value
+                       tmp_node_pos = self.nodepos[commit.parent_sha1[0]]
+               except KeyError:
+                       self.colours[commit.parent_sha1[0]] = colour
+                       self.nodepos[commit.parent_sha1[0]] = node_pos
+
+               for sha1 in self.incomplete_line.keys():
+                       if (sha1 != commit.commit_sha1):
+                               self.draw_incomplete_line(sha1, node_pos,
+                                               out_line, in_line, index)
+                       else:
+                               del self.incomplete_line[sha1]
+
+
+               for parent_id in commit.parent_sha1:
+                       try:
+                               tmp_node_pos = self.nodepos[parent_id]
+                       except KeyError:
+                               self.colours[parent_id] = last_colour+1
+                               last_colour = self.colours[parent_id]
+                               self.nodepos[parent_id] = last_nodepos+1
+                               last_nodepos = self.nodepos[parent_id]
+
+                       in_line.append((node_pos, self.nodepos[parent_id],
+                                               self.colours[parent_id]))
+                       self.add_incomplete_line(parent_id)
+
+               try:
+                       branch_tag = self.bt_sha1[commit.commit_sha1]
+               except KeyError:
+                       branch_tag = [ ]
+
+
+               node = (node_pos, colour, branch_tag)
+
+               self.model.append([commit, node, out_line, in_line,
+                               commit.message, commit.author, commit.date])
+
+               return (in_line, last_colour, last_nodepos)
+
+       def add_incomplete_line(self, sha1):
+               try:
+                       self.incomplete_line[sha1].append(self.nodepos[sha1])
+               except KeyError:
+                       self.incomplete_line[sha1] = [self.nodepos[sha1]]
+
+       def draw_incomplete_line(self, sha1, node_pos, out_line, in_line, index):
+               for idx, pos in enumerate(self.incomplete_line[sha1]):
+                       if(pos == node_pos):
+                               #remove the straight line and add a slash
+                               if ((pos, pos, self.colours[sha1]) in out_line):
+                                       out_line.remove((pos, pos, self.colours[sha1]))
+                               out_line.append((pos, pos+0.5, self.colours[sha1]))
+                               self.incomplete_line[sha1][idx] = pos = pos+0.5
+                       try:
+                               next_commit = self.commits[index+1]
+                               if (next_commit.commit_sha1 == sha1 and pos != int(pos)):
+                               # join the line back to the node point
+                               # This need to be done only if we modified it
+                                       in_line.append((pos, pos-0.5, self.colours[sha1]))
+                                       continue;
+                       except IndexError:
+                               pass
+                       in_line.append((pos, pos, self.colours[sha1]))
+
+
+       def _go_clicked_cb(self, widget, revid):
+               """Callback for when the go button for a parent is clicked."""
+               try:
+                       self.treeview.set_cursor(self.index[revid])
+               except KeyError:
+                       dialog = gtk.MessageDialog(parent=None, flags=0,
+                                       type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_CLOSE,
+                                       message_format=None)
+                       dialog.set_markup("Revision <b>%s</b> not present in the list" % revid)
+                       # revid == 0 is the parent of the first commit
+                       if (revid != 0 ):
+                               dialog.format_secondary_text("Try running gitview without any options")
+                       dialog.run()
+                       dialog.destroy()
+
+               self.treeview.grab_focus()
+
+       def _show_clicked_cb(self, widget,  commit_sha1, parent_sha1, encoding):
+               """Callback for when the show button for a parent is clicked."""
+               window = DiffWindow()
+               window.set_diff(commit_sha1, parent_sha1, encoding)
+               self.treeview.grab_focus()
+
+without_diff = 0
+if __name__ == "__main__":
+
+       if (len(sys.argv) > 1 ):
+               if (sys.argv[1] == "--without-diff"):
+                       without_diff = 1
+
+       view = GitView( without_diff != 1)
+       view.run(sys.argv[without_diff:])
+
+
diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt
new file mode 100644 (file)
index 0000000..6924df2
--- /dev/null
@@ -0,0 +1,46 @@
+gitview(1)
+==========
+
+NAME
+----
+gitview - A GTK based repository browser for git
+
+SYNOPSIS
+--------
+'gitview'  [options] [args]
+
+DESCRIPTION
+---------
+
+Dependencies
+
+* Python 2.4
+* PyGTK 2.8 or later
+* PyCairo 1.0 or later
+
+OPTIONS
+------
+       --without-diff
+               If the user doesn't want to list the commit diffs in the main window. This may speed up the repository browsing.
+
+       <args>
+               All the valid option for git-rev-list(1)
+       Key Bindings:
+       F4:
+               To maximize the window
+       F5:
+               To reread references.
+       F11:
+               Full screen
+       F12:
+               Leave full screen
+
+EXAMPLES
+------
+       gitview v2.6.12.. include/scsi drivers/scsi
+         Show as the changes since version v2.6.12 that changed any file in the include/scsi
+         or drivers/scsi subdirectories
+
+       gitview --since=2.weeks.ago
+         Show the changes during the last two weeks
+
diff --git a/contrib/remotes2config.sh b/contrib/remotes2config.sh
new file mode 100644 (file)
index 0000000..25901e2
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# Use this tool to rewrite your .git/remotes/ files into the config.
+
+. git-sh-setup
+
+if [ -d "$GIT_DIR"/remotes ]; then
+       echo "Rewriting $GIT_DIR/remotes" >&2
+       error=0
+       # rewrite into config
+       {
+               cd "$GIT_DIR"/remotes
+               ls | while read f; do
+                       name=$(echo -n "$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" \
+                       -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \
+                       < "$f"
+               done
+               echo done
+       } | while read key value regex; do
+               case $key in
+               done)
+                       if [ $error = 0 ]; 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 ;;
+               esac
+       done
+fi
+
+
diff --git a/convert-objects.c b/convert-objects.c
new file mode 100644 (file)
index 0000000..a67d6b4
--- /dev/null
@@ -0,0 +1,332 @@
+#define _XOPEN_SOURCE 500 /* glibc2 and AIX 5.3L need this */
+#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
+#include <time.h>
+#include "cache.h"
+#include "blob.h"
+#include "commit.h"
+#include "tree.h"
+
+struct entry {
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       int converted;
+};
+
+#define MAXOBJECTS (1000000)
+
+static struct entry *convert[MAXOBJECTS];
+static int nr_convert;
+
+static struct entry * convert_entry(unsigned char *sha1);
+
+static struct entry *insert_new(unsigned char *sha1, int pos)
+{
+       struct entry *new = xcalloc(1, sizeof(struct entry));
+       memcpy(new->old_sha1, sha1, 20);
+       memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
+       convert[pos] = new;
+       nr_convert++;
+       if (nr_convert == MAXOBJECTS)
+               die("you're kidding me - hit maximum object limit");
+       return new;
+}
+
+static struct entry *lookup_entry(unsigned char *sha1)
+{
+       int low = 0, high = nr_convert;
+
+       while (low < high) {
+               int next = (low + high) / 2;
+               struct entry *n = convert[next];
+               int cmp = memcmp(sha1, n->old_sha1, 20);
+               if (!cmp)
+                       return n;
+               if (cmp < 0) {
+                       high = next;
+                       continue;
+               }
+               low = next+1;
+       }
+       return insert_new(sha1, low);
+}
+
+static void convert_binary_sha1(void *buffer)
+{
+       struct entry *entry = convert_entry(buffer);
+       memcpy(buffer, entry->new_sha1, 20);
+}
+
+static void convert_ascii_sha1(void *buffer)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       if (get_sha1_hex(buffer, sha1))
+               die("expected sha1, got '%s'", (char*) buffer);
+       entry = convert_entry(sha1);
+       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
+}
+
+static unsigned int convert_mode(unsigned int mode)
+{
+       unsigned int newmode;
+
+       newmode = mode & S_IFMT;
+       if (S_ISREG(mode))
+               newmode |= (mode & 0100) ? 0755 : 0644;
+       return newmode;
+}
+
+static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size);
+       unsigned long newlen = 0;
+       unsigned long used;
+
+       used = 0;
+       while (size) {
+               int len = 21 + strlen(buffer);
+               char *path = strchr(buffer, ' ');
+               unsigned char *sha1;
+               unsigned int mode;
+               char *slash, *origpath;
+
+               if (!path || sscanf(buffer, "%o", &mode) != 1)
+                       die("bad tree conversion");
+               mode = convert_mode(mode);
+               path++;
+               if (memcmp(path, base, baselen))
+                       break;
+               origpath = path;
+               path += baselen;
+               slash = strchr(path, '/');
+               if (!slash) {
+                       newlen += sprintf(new + newlen, "%o %s", mode, path);
+                       new[newlen++] = '\0';
+                       memcpy(new + newlen, buffer + len - 20, 20);
+                       newlen += 20;
+
+                       used += len;
+                       size -= len;
+                       buffer += len;
+                       continue;
+               }
+
+               newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
+               new[newlen++] = 0;
+               sha1 = (unsigned char *)(new + newlen);
+               newlen += 20;
+
+               len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
+
+               used += len;
+               size -= len;
+               buffer += len;
+       }
+
+       write_sha1_file(new, newlen, tree_type, result_sha1);
+       free(new);
+       return used;
+}
+
+static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       while (size) {
+               int len = 1+strlen(buffer);
+
+               convert_binary_sha1(buffer + len);
+
+               len += 20;
+               if (len > size)
+                       die("corrupt tree object");
+               size -= len;
+               buffer += len;
+       }
+
+       write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
+}
+
+static unsigned long parse_oldstyle_date(const char *buf)
+{
+       char c, *p;
+       char buffer[100];
+       struct tm tm;
+       const char *formats[] = {
+               "%c",
+               "%a %b %d %T",
+               "%Z",
+               "%Y",
+               " %Y",
+               NULL
+       };
+       /* We only ever did two timezones in the bad old format .. */
+       const char *timezones[] = {
+               "PDT", "PST", "CEST", NULL
+       };
+       const char **fmt = formats;
+
+       p = buffer;
+       while (isspace(c = *buf))
+               buf++;
+       while ((c = *buf++) != '\n')
+               *p++ = c;
+       *p++ = 0;
+       buf = buffer;
+       memset(&tm, 0, sizeof(tm));
+       do {
+               const char *next = strptime(buf, *fmt, &tm);
+               if (next) {
+                       if (!*next)
+                               return mktime(&tm);
+                       buf = next;
+               } else {
+                       const char **p = timezones;
+                       while (isspace(*buf))
+                               buf++;
+                       while (*p) {
+                               if (!memcmp(buf, *p, strlen(*p))) {
+                                       buf += strlen(*p);
+                                       break;
+                               }
+                               p++;
+                       }
+               }
+               fmt++;
+       } while (*buf && *fmt);
+       printf("left: %s\n", buf);
+       return mktime(&tm);                             
+}
+
+static int convert_date_line(char *dst, void **buf, unsigned long *sp)
+{
+       unsigned long size = *sp;
+       char *line = *buf;
+       char *next = strchr(line, '\n');
+       char *date = strchr(line, '>');
+       int len;
+
+       if (!next || !date)
+               die("missing or bad author/committer line %s", line);
+       next++; date += 2;
+
+       *buf = next;
+       *sp = size - (next - line);
+
+       len = date - line;
+       memcpy(dst, line, len);
+       dst += len;
+
+       /* Is it already in new format? */
+       if (isdigit(*date)) {
+               int datelen = next - date;
+               memcpy(dst, date, datelen);
+               return len + datelen;
+       }
+
+       /*
+        * Hacky hacky: one of the sparse old-style commits does not have
+        * any date at all, but we can fake it by using the committer date.
+        */
+       if (*date == '\n' && strchr(next, '>'))
+               date = strchr(next, '>')+2;
+
+       return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
+}
+
+static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size + 100);
+       unsigned long newlen = 0;
+       
+       // "tree <sha1>\n"
+       memcpy(new + newlen, buffer, 46);
+       newlen += 46;
+       buffer += 46;
+       size -= 46;
+
+       // "parent <sha1>\n"
+       while (!memcmp(buffer, "parent ", 7)) {
+               memcpy(new + newlen, buffer, 48);
+               newlen += 48;
+               buffer += 48;
+               size -= 48;
+       }
+
+       // "author xyz <xyz> date"
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+       // "committer xyz <xyz> date"
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+
+       // Rest
+       memcpy(new + newlen, buffer, size);
+       newlen += size;
+
+       write_sha1_file(new, newlen, commit_type, result_sha1);
+       free(new);
+}
+
+static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       if (memcmp(buffer, "tree ", 5))
+               die("Bad commit '%s'", (char*) buffer);
+       convert_ascii_sha1(buffer+5);
+       buffer += 46;    /* "tree " + "hex sha1" + "\n" */
+       while (!memcmp(buffer, "parent ", 7)) {
+               convert_ascii_sha1(buffer+7);
+               buffer += 48;
+       }
+       convert_date(orig_buffer, orig_size, result_sha1);
+}
+
+static struct entry * convert_entry(unsigned char *sha1)
+{
+       struct entry *entry = lookup_entry(sha1);
+       char type[20];
+       void *buffer, *data;
+       unsigned long size;
+
+       if (entry->converted)
+               return entry;
+       data = read_sha1_file(sha1, type, &size);
+       if (!data)
+               die("unable to read object %s", sha1_to_hex(sha1));
+
+       buffer = xmalloc(size);
+       memcpy(buffer, data, size);
+
+       if (!strcmp(type, blob_type)) {
+               write_sha1_file(buffer, size, blob_type, entry->new_sha1);
+       } else if (!strcmp(type, tree_type))
+               convert_tree(buffer, size, entry->new_sha1);
+       else if (!strcmp(type, commit_type))
+               convert_commit(buffer, size, entry->new_sha1);
+       else
+               die("unknown object type '%s' in %s", type, sha1_to_hex(sha1));
+       entry->converted = 1;
+       free(buffer);
+       free(data);
+       return entry;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       setup_git_directory();
+
+       if (argc != 2)
+               usage("git-convert-objects <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       entry = convert_entry(sha1);
+       printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
+       return 0;
+}
diff --git a/copy.c b/copy.c
new file mode 100644 (file)
index 0000000..08a3d38
--- /dev/null
+++ b/copy.c
@@ -0,0 +1,38 @@
+#include "cache.h"
+
+int copy_fd(int ifd, int ofd)
+{
+       while (1) {
+               int len;
+               char buffer[8192];
+               char *buf = buffer;
+               len = xread(ifd, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (len < 0) {
+                       int read_error;
+                       read_error = errno;
+                       close(ifd);
+                       return error("copy-fd: read returned %s",
+                                    strerror(read_error));
+               }
+               while (len) {
+                       int written = xwrite(ofd, buf, len);
+                       if (written > 0) {
+                               buf += written;
+                               len -= written;
+                       }
+                       else if (!written) {
+                               close(ifd);
+                               return error("copy-fd: write returned 0");
+                       } else {
+                               close(ifd);
+                               return error("copy-fd: write returned %s",
+                                            strerror(errno));
+                       }
+               }
+       }
+       close(ifd);
+       return 0;
+}
+
diff --git a/csum-file.c b/csum-file.c
new file mode 100644 (file)
index 0000000..5f9249a
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * csum-file.c
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Simple file write infrastructure for writing SHA1-summed
+ * files. Useful when you write a file that you want to be
+ * able to verify hasn't been messed with afterwards.
+ */
+#include "cache.h"
+#include "csum-file.h"
+
+static int sha1flush(struct sha1file *f, unsigned int count)
+{
+       void *buf = f->buffer;
+
+       for (;;) {
+               int ret = xwrite(f->fd, buf, count);
+               if (ret > 0) {
+                       buf += ret;
+                       count -= ret;
+                       if (count)
+                               continue;
+                       return 0;
+               }
+               if (!ret)
+                       die("sha1 file '%s' write error. Out of diskspace", f->name);
+               die("sha1 file '%s' write error (%s)", f->name, strerror(errno));
+       }
+}
+
+int sha1close(struct sha1file *f, unsigned char *result, int update)
+{
+       unsigned offset = f->offset;
+       if (offset) {
+               SHA1_Update(&f->ctx, f->buffer, offset);
+               sha1flush(f, offset);
+       }
+       SHA1_Final(f->buffer, &f->ctx);
+       if (result)
+               memcpy(result, f->buffer, 20);
+       if (update)
+               sha1flush(f, 20);
+       if (close(f->fd))
+               die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
+       free(f);
+       return 0;
+}
+
+int sha1write(struct sha1file *f, void *buf, unsigned int count)
+{
+       while (count) {
+               unsigned offset = f->offset;
+               unsigned left = sizeof(f->buffer) - offset;
+               unsigned nr = count > left ? left : count;
+
+               memcpy(f->buffer + offset, buf, nr);
+               count -= nr;
+               offset += nr;
+               buf += nr;
+               left -= nr;
+               if (!left) {
+                       SHA1_Update(&f->ctx, f->buffer, offset);
+                       sha1flush(f, offset);
+                       offset = 0;
+               }
+               f->offset = offset;
+       }
+       return 0;
+}
+
+struct sha1file *sha1create(const char *fmt, ...)
+{
+       struct sha1file *f;
+       unsigned len;
+       va_list arg;
+       int fd;
+
+       f = xmalloc(sizeof(*f));
+
+       va_start(arg, fmt);
+       len = vsnprintf(f->name, sizeof(f->name), fmt, arg);
+       va_end(arg);
+       if (len >= PATH_MAX)
+               die("you wascally wabbit, you");
+       f->namelen = len;
+
+       fd = open(f->name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0)
+               die("unable to open %s (%s)", f->name, strerror(errno));
+       f->fd = fd;
+       f->error = 0;
+       f->offset = 0;
+       SHA1_Init(&f->ctx);
+       return f;
+}
+
+struct sha1file *sha1fd(int fd, const char *name)
+{
+       struct sha1file *f;
+       unsigned len;
+
+       f = xmalloc(sizeof(*f));
+
+       len = strlen(name);
+       if (len >= PATH_MAX)
+               die("you wascally wabbit, you");
+       f->namelen = len;
+       memcpy(f->name, name, len+1);
+
+       f->fd = fd;
+       f->error = 0;
+       f->offset = 0;
+       SHA1_Init(&f->ctx);
+       return f;
+}
+
+int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
+{
+       z_stream stream;
+       unsigned long maxsize;
+       void *out;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+       maxsize = deflateBound(&stream, size);
+       out = xmalloc(maxsize);
+
+       /* Compress it */
+       stream.next_in = in;
+       stream.avail_in = size;
+
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+
+       size = stream.total_out;
+       sha1write(f, out, size);
+       free(out);
+       return size;
+}
+
+
diff --git a/csum-file.h b/csum-file.h
new file mode 100644 (file)
index 0000000..3ad1a99
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef CSUM_FILE_H
+#define CSUM_FILE_H
+
+/* A SHA1-protected file */
+struct sha1file {
+       int fd, error;
+       unsigned int offset, namelen;
+       SHA_CTX ctx;
+       char name[PATH_MAX];
+       unsigned char buffer[8192];
+};
+
+extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern int sha1close(struct sha1file *, unsigned char *, int);
+extern int sha1write(struct sha1file *, void *, unsigned int);
+extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
+
+#endif
diff --git a/ctype.c b/ctype.c
new file mode 100644 (file)
index 0000000..56bdffa
--- /dev/null
+++ b/ctype.c
@@ -0,0 +1,23 @@
+/*
+ * Sane locale-independent, ASCII ctype.
+ *
+ * No surprises, and works with signed and unsigned chars.
+ */
+#include "cache.h"
+
+#define SS GIT_SPACE
+#define AA GIT_ALPHA
+#define DD GIT_DIGIT
+
+unsigned char sane_ctype[256] = {
+        0,  0,  0,  0,  0,  0,  0,  0,  0, SS, SS,  0,  0, SS,  0,  0,         /* 0-15 */
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 16-15 */
+       SS,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,         /* 32-15 */
+       DD, DD, DD, DD, DD, DD, DD, DD, DD, DD,  0,  0,  0,  0,  0,  0,         /* 48-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 64-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 80-15 */
+        0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,         /* 96-15 */
+       AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 112-15 */
+       /* Nothing in the 128.. range */
+};
+
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..2f03f99
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,759 @@
+#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 "pkt-line.h"
+#include "cache.h"
+#include "exec_cmd.h"
+
+static int log_syslog;
+static int verbose;
+static int reuseaddr;
+
+static const char daemon_usage[] =
+"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+"           [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
+"           [--base-path=path] [--user-path | --user-path=path]\n"
+"           [--reuseaddr] [directory...]";
+
+/* List of acceptable pathname prefixes */
+static char **ok_paths = NULL;
+static int strict_paths = 0;
+
+/* If this is set, git-daemon-export-ok is not required */
+static int export_all_trees = 0;
+
+/* Take all paths relative to this one if non-NULL */
+static char *base_path = NULL;
+
+/* If defined, ~user notation is allowed and the string is inserted
+ * after ~user/.  E.g. a request to git://host/~alice/frotz would
+ * go to /home/alice/pub_git/frotz with --user-path=pub_git.
+ */
+static char *user_path = NULL;
+
+/* Timeout, and initial timeout */
+static unsigned int timeout = 0;
+static unsigned int init_timeout = 0;
+
+static void logreport(int priority, const char *err, va_list params)
+{
+       /* We should do a single write so that it is atomic and output
+        * of several processes do not get intermingled. */
+       char buf[1024];
+       int buflen;
+       int maxlen, msglen;
+
+       /* sizeof(buf) should be big enough for "[pid] \n" */
+       buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
+
+       maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
+       msglen = vsnprintf(buf + buflen, maxlen, err, params);
+
+       if (log_syslog) {
+               syslog(priority, "%s", buf);
+               return;
+       }
+
+       /* maxlen counted our own LF but also counts space given to
+        * vsnprintf for the terminating NUL.  We want to make sure that
+        * we have space for our own LF and NUL after the "meat" of the
+        * message, so truncate it at maxlen - 1.
+        */
+       if (msglen > maxlen - 1)
+               msglen = maxlen - 1;
+       else if (msglen < 0)
+               msglen = 0; /* Protect against weird return values. */
+       buflen += msglen;
+
+       buf[buflen++] = '\n';
+       buf[buflen] = '\0';
+
+       write(2, buf, buflen);
+}
+
+static void logerror(const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       logreport(LOG_ERR, err, params);
+       va_end(params);
+}
+
+static void loginfo(const char *err, ...)
+{
+       va_list params;
+       if (!verbose)
+               return;
+       va_start(params, err);
+       logreport(LOG_INFO, err, params);
+       va_end(params);
+}
+
+static int avoid_alias(char *p)
+{
+       int sl, ndot;
+
+       /* 
+        * This resurrects the belts and suspenders paranoia check by HPA
+        * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
+        * does not do getcwd() based path canonicalizations.
+        *
+        * sl becomes true immediately after seeing '/' and continues to
+        * be true as long as dots continue after that without intervening
+        * non-dot character.
+        */
+       if (!p || (*p != '/' && *p != '~'))
+               return -1;
+       sl = 1; ndot = 0;
+       p++;
+
+       while (1) {
+               char ch = *p++;
+               if (sl) {
+                       if (ch == '.')
+                               ndot++;
+                       else if (ch == '/') {
+                               if (ndot < 3)
+                                       /* reject //, /./ and /../ */
+                                       return -1;
+                               ndot = 0;
+                       }
+                       else if (ch == 0) {
+                               if (0 < ndot && ndot < 3)
+                                       /* reject /.$ and /..$ */
+                                       return -1;
+                               return 0;
+                       }
+                       else
+                               sl = ndot = 0;
+               }
+               else if (ch == 0)
+                       return 0;
+               else if (ch == '/') {
+                       sl = 1;
+                       ndot = 0;
+               }
+       }
+}
+
+static char *path_ok(char *dir)
+{
+       static char rpath[PATH_MAX];
+       char *path;
+
+       if (avoid_alias(dir)) {
+               logerror("'%s': aliased", dir);
+               return NULL;
+       }
+
+       if (*dir == '~') {
+               if (!user_path) {
+                       logerror("'%s': User-path not allowed", dir);
+                       return NULL;
+               }
+               if (*user_path) {
+                       /* Got either "~alice" or "~alice/foo";
+                        * rewrite them to "~alice/%s" or
+                        * "~alice/%s/foo".
+                        */
+                       int namlen, restlen = strlen(dir);
+                       char *slash = strchr(dir, '/');
+                       if (!slash)
+                               slash = dir + restlen;
+                       namlen = slash - dir;
+                       restlen -= namlen;
+                       loginfo("userpath <%s>, request <%s>, namlen %d, restlen %d, slash <%s>", user_path, dir, namlen, restlen, slash);
+                       snprintf(rpath, PATH_MAX, "%.*s/%s%.*s",
+                                namlen, dir, user_path, restlen, slash);
+                       dir = rpath;
+               }
+       }
+       else if (base_path) {
+               if (*dir != '/') {
+                       /* Allow only absolute */
+                       logerror("'%s': Non-absolute path denied (base-path active)", dir);
+                       return NULL;
+               }
+               else {
+                       snprintf(rpath, PATH_MAX, "%s%s", base_path, dir);
+                       dir = rpath;
+               }
+       }
+
+       path = enter_repo(dir, strict_paths);
+
+       if (!path) {
+               logerror("'%s': unable to chdir or not a git archive", dir);
+               return NULL;
+       }
+
+       if ( ok_paths && *ok_paths ) {
+               char **pp;
+               int pathlen = strlen(path);
+
+               /* The validation is done on the paths after enter_repo
+                * appends optional {.git,.git/.git} and friends, but 
+                * it does not use getcwd().  So if your /pub is
+                * a symlink to /mnt/pub, you can whitelist /pub and
+                * do not have to say /mnt/pub.
+                * Do not say /pub/.
+                */
+               for ( pp = ok_paths ; *pp ; pp++ ) {
+                       int len = strlen(*pp);
+                       if (len <= pathlen &&
+                           !memcmp(*pp, path, len) &&
+                           (path[len] == '\0' ||
+                            (!strict_paths && path[len] == '/')))
+                               return path;
+               }
+       }
+       else {
+               /* be backwards compatible */
+               if (!strict_paths)
+                       return path;
+       }
+
+       logerror("'%s': not in whitelist", path);
+       return NULL;            /* Fallthrough. Deny by default */
+}
+
+static int upload(char *dir)
+{
+       /* Timeout as string */
+       char timeout_buf[64];
+       const char *path;
+
+       loginfo("Request for '%s'", dir);
+
+       if (!(path = path_ok(dir)))
+               return -1;
+
+       /*
+        * Security on the cheap.
+        *
+        * We want a readable HEAD, usable "objects" directory, and
+        * a "git-daemon-export-ok" flag that says that the other side
+        * is ok with us doing this.
+        *
+        * path_ok() uses enter_repo() and does whitelist checking.
+        * We only need to make sure the repository is exported.
+        */
+
+       if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+               logerror("'%s': repository not exported.", path);
+               errno = EACCES;
+               return -1;
+       }
+
+       /*
+        * We'll ignore SIGTERM from now on, we have a
+        * good client.
+        */
+       signal(SIGTERM, SIG_IGN);
+
+       snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+
+       /* git-upload-pack only ever reads stuff, so this is safe */
+       execl_git_cmd("upload-pack", "--strict", timeout_buf, ".", NULL);
+       return -1;
+}
+
+static int execute(void)
+{
+       static char line[1000];
+       int pktlen, len;
+
+       alarm(init_timeout ? init_timeout : timeout);
+       pktlen = packet_read_line(0, line, sizeof(line));
+       alarm(0);
+
+       len = strlen(line);
+       if (pktlen != len)
+               loginfo("Extended attributes (%d bytes) exist <%.*s>",
+                       (int) pktlen - len,
+                       (int) pktlen - len, line + len + 1);
+       if (len && line[len-1] == '\n')
+               line[--len] = 0;
+
+       if (!strncmp("git-upload-pack ", line, 16))
+               return upload(line+16);
+
+       logerror("Protocol error: '%s'", line);
+       return -1;
+}
+
+
+/*
+ * We count spawned/reaped separately, just to avoid any
+ * races when updating them from signals. The SIGCHLD handler
+ * will only update children_reaped, and the fork logic will
+ * only update children_spawned.
+ *
+ * MAX_CHILDREN should be a power-of-two to make the modulus
+ * operation cheap. It should also be at least twice
+ * the maximum number of connections we will ever allow.
+ */
+#define MAX_CHILDREN 128
+
+static int max_connections = 25;
+
+/* These are updated by the signal handler */
+static volatile unsigned int children_reaped = 0;
+static pid_t dead_child[MAX_CHILDREN];
+
+/* These are updated by the main loop */
+static unsigned int children_spawned = 0;
+static unsigned int children_deleted = 0;
+
+static struct child {
+       pid_t pid;
+       int addrlen;
+       struct sockaddr_storage address;
+} live_child[MAX_CHILDREN];
+
+static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
+{
+       live_child[idx].pid = pid;
+       live_child[idx].addrlen = addrlen;
+       memcpy(&live_child[idx].address, addr, addrlen);
+}
+
+/*
+ * Walk from "deleted" to "spawned", and remove child "pid".
+ *
+ * We move everything up by one, since the new "deleted" will
+ * be one higher.
+ */
+static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
+{
+       struct child n;
+
+       deleted %= MAX_CHILDREN;
+       spawned %= MAX_CHILDREN;
+       if (live_child[deleted].pid == pid) {
+               live_child[deleted].pid = -1;
+               return;
+       }
+       n = live_child[deleted];
+       for (;;) {
+               struct child m;
+               deleted = (deleted + 1) % MAX_CHILDREN;
+               if (deleted == spawned)
+                       die("could not find dead child %d\n", pid);
+               m = live_child[deleted];
+               live_child[deleted] = n;
+               if (m.pid == pid)
+                       return;
+               n = m;
+       }
+}
+
+/*
+ * This gets called if the number of connections grows
+ * past "max_connections".
+ *
+ * We _should_ start off by searching for connections
+ * from the same IP, and if there is some address wth
+ * multiple connections, we should kill that first.
+ *
+ * As it is, we just "randomly" kill 25% of the connections,
+ * and our pseudo-random generator sucks too. I have no
+ * shame.
+ *
+ * Really, this is just a place-holder for a _real_ algorithm.
+ */
+static void kill_some_children(int signo, unsigned start, unsigned stop)
+{
+       start %= MAX_CHILDREN;
+       stop %= MAX_CHILDREN;
+       while (start != stop) {
+               if (!(start & 3))
+                       kill(live_child[start].pid, signo);
+               start = (start + 1) % MAX_CHILDREN;
+       }
+}
+
+static void check_max_connections(void)
+{
+       for (;;) {
+               int active;
+               unsigned spawned, reaped, deleted;
+
+               spawned = children_spawned;
+               reaped = children_reaped;
+               deleted = children_deleted;
+
+               while (deleted < reaped) {
+                       pid_t pid = dead_child[deleted % MAX_CHILDREN];
+                       remove_child(pid, deleted, spawned);
+                       deleted++;
+               }
+               children_deleted = deleted;
+
+               active = spawned - deleted;
+               if (active <= max_connections)
+                       break;
+
+               /* Kill some unstarted connections with SIGTERM */
+               kill_some_children(SIGTERM, deleted, spawned);
+               if (active <= max_connections << 1)
+                       break;
+
+               /* If the SIGTERM thing isn't helping use SIGKILL */
+               kill_some_children(SIGKILL, deleted, spawned);
+               sleep(1);
+       }
+}
+
+static void handle(int incoming, struct sockaddr *addr, int addrlen)
+{
+       pid_t pid = fork();
+       char addrbuf[256] = "";
+       int port = -1;
+
+       if (pid) {
+               unsigned idx;
+
+               close(incoming);
+               if (pid < 0)
+                       return;
+
+               idx = children_spawned % MAX_CHILDREN;
+               children_spawned++;
+               add_child(idx, pid, addr, addrlen);
+
+               check_max_connections();
+               return;
+       }
+
+       dup2(incoming, 0);
+       dup2(incoming, 1);
+       close(incoming);
+
+       if (addr->sa_family == AF_INET) {
+               struct sockaddr_in *sin_addr = (void *) addr;
+               inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
+               port = sin_addr->sin_port;
+
+#ifndef NO_IPV6
+       } else if (addr->sa_family == AF_INET6) {
+               struct sockaddr_in6 *sin6_addr = (void *) addr;
+
+               char *buf = addrbuf;
+               *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+               inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
+               strcat(buf, "]");
+
+               port = sin6_addr->sin6_port;
+#endif
+       }
+       loginfo("Connection from %s:%d", addrbuf, port);
+
+       exit(execute());
+}
+
+static void child_handler(int signo)
+{
+       for (;;) {
+               int status;
+               pid_t pid = waitpid(-1, &status, WNOHANG);
+
+               if (pid > 0) {
+                       unsigned reaped = children_reaped;
+                       dead_child[reaped % MAX_CHILDREN] = pid;
+                       children_reaped = reaped + 1;
+                       /* XXX: Custom logging, since we don't wanna getpid() */
+                       if (verbose) {
+                               char *dead = "";
+                               if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+                                       dead = " (with error)";
+                               if (log_syslog)
+                                       syslog(LOG_INFO, "[%d] Disconnected%s", pid, dead);
+                               else
+                                       fprintf(stderr, "[%d] Disconnected%s\n", pid, dead);
+                       }
+                       continue;
+               }
+               break;
+       }
+}
+
+static int set_reuse_addr(int sockfd)
+{
+       int on = 1;
+
+       if (!reuseaddr)
+               return 0;
+       return setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+                         &on, sizeof(on));
+}
+
+#ifndef NO_IPV6
+
+static int socksetup(int port, int **socklist_p)
+{
+       int socknum = 0, *socklist = NULL;
+       int maxfd = -1;
+       char pbuf[NI_MAXSERV];
+
+       struct addrinfo hints, *ai0, *ai;
+       int gai;
+
+       sprintf(pbuf, "%d", port);
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+       hints.ai_flags = AI_PASSIVE;
+
+       gai = getaddrinfo(NULL, pbuf, &hints, &ai0);
+       if (gai)
+               die("getaddrinfo() failed: %s\n", gai_strerror(gai));
+
+       for (ai = ai0; ai; ai = ai->ai_next) {
+               int sockfd;
+               int *newlist;
+
+               sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+               if (sockfd < 0)
+                       continue;
+               if (sockfd >= FD_SETSIZE) {
+                       error("too large socket descriptor.");
+                       close(sockfd);
+                       continue;
+               }
+
+#ifdef IPV6_V6ONLY
+               if (ai->ai_family == AF_INET6) {
+                       int on = 1;
+                       setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                  &on, sizeof(on));
+                       /* Note: error is not fatal */
+               }
+#endif
+
+               if (set_reuse_addr(sockfd)) {
+                       close(sockfd);
+                       continue;
+               }
+
+               if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       close(sockfd);
+                       continue;       /* not fatal */
+               }
+               if (listen(sockfd, 5) < 0) {
+                       close(sockfd);
+                       continue;       /* not fatal */
+               }
+
+               newlist = realloc(socklist, sizeof(int) * (socknum + 1));
+               if (!newlist)
+                       die("memory allocation failed: %s", strerror(errno));
+
+               socklist = newlist;
+               socklist[socknum++] = sockfd;
+
+               if (maxfd < sockfd)
+                       maxfd = sockfd;
+       }
+
+       freeaddrinfo(ai0);
+
+       *socklist_p = socklist;
+       return socknum;
+}
+
+#else /* NO_IPV6 */
+
+static int socksetup(int port, int **socklist_p)
+{
+       struct sockaddr_in sin;
+       int sockfd;
+
+       sockfd = socket(AF_INET, SOCK_STREAM, 0);
+       if (sockfd < 0)
+               return 0;
+
+       memset(&sin, 0, sizeof sin);
+       sin.sin_family = AF_INET;
+       sin.sin_addr.s_addr = htonl(INADDR_ANY);
+       sin.sin_port = htons(port);
+
+       if (set_reuse_addr(sockfd)) {
+               close(sockfd);
+               return 0;
+       }
+
+       if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+               close(sockfd);
+               return 0;
+       }
+
+       if (listen(sockfd, 5) < 0) {
+               close(sockfd);
+               return 0;
+       }
+
+       *socklist_p = xmalloc(sizeof(int));
+       **socklist_p = sockfd;
+       return 1;
+}
+
+#endif
+
+static int service_loop(int socknum, int *socklist)
+{
+       struct pollfd *pfd;
+       int i;
+
+       pfd = xcalloc(socknum, sizeof(struct pollfd));
+
+       for (i = 0; i < socknum; i++) {
+               pfd[i].fd = socklist[i];
+               pfd[i].events = POLLIN;
+       }
+
+       signal(SIGCHLD, child_handler);
+
+       for (;;) {
+               int i;
+
+               if (poll(pfd, socknum, -1) < 0) {
+                       if (errno != EINTR) {
+                               error("poll failed, resuming: %s",
+                                     strerror(errno));
+                               sleep(1);
+                       }
+                       continue;
+               }
+
+               for (i = 0; i < socknum; i++) {
+                       if (pfd[i].revents & POLLIN) {
+                               struct sockaddr_storage ss;
+                               unsigned int sslen = sizeof(ss);
+                               int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
+                               if (incoming < 0) {
+                                       switch (errno) {
+                                       case EAGAIN:
+                                       case EINTR:
+                                       case ECONNABORTED:
+                                               continue;
+                                       default:
+                                               die("accept returned %s", strerror(errno));
+                                       }
+                               }
+                               handle(incoming, (struct sockaddr *)&ss, sslen);
+                       }
+               }
+       }
+}
+
+static int serve(int port)
+{
+       int socknum, *socklist;
+
+       socknum = socksetup(port, &socklist);
+       if (socknum == 0)
+               die("unable to allocate any listen sockets on port %u", port);
+
+       return service_loop(socknum, socklist);
+}
+
+int main(int argc, char **argv)
+{
+       int port = DEFAULT_GIT_PORT;
+       int inetd_mode = 0;
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (!strncmp(arg, "--port=", 7)) {
+                       char *end;
+                       unsigned long n;
+                       n = strtoul(arg+7, &end, 0);
+                       if (arg[7] && !*end) {
+                               port = n;
+                               continue;
+                       }
+               }
+               if (!strcmp(arg, "--inetd")) {
+                       inetd_mode = 1;
+                       log_syslog = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--verbose")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--syslog")) {
+                       log_syslog = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--export-all")) {
+                       export_all_trees = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--timeout=", 10)) {
+                       timeout = atoi(arg+10);
+                       continue;
+               }
+               if (!strncmp(arg, "--init-timeout=", 15)) {
+                       init_timeout = atoi(arg+15);
+                       continue;
+               }
+               if (!strcmp(arg, "--strict-paths")) {
+                       strict_paths = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--base-path=", 12)) {
+                       base_path = arg+12;
+                       continue;
+               }
+               if (!strcmp(arg, "--reuseaddr")) {
+                       reuseaddr = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--user-path")) {
+                       user_path = "";
+                       continue;
+               }
+               if (!strncmp(arg, "--user-path=", 12)) {
+                       user_path = arg + 12;
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       ok_paths = &argv[i+1];
+                       break;
+               } else if (arg[0] != '-') {
+                       ok_paths = &argv[i];
+                       break;
+               }
+
+               usage(daemon_usage);
+       }
+
+       if (log_syslog)
+               openlog("git-daemon", 0, LOG_DAEMON);
+
+       if (strict_paths && (!ok_paths || !*ok_paths)) {
+               if (!inetd_mode)
+                       die("git-daemon: option --strict-paths requires a whitelist");
+
+               logerror("option --strict-paths requires a whitelist");
+               exit (1);
+       }
+
+       if (inetd_mode) {
+               fclose(stderr); //FIXME: workaround
+               return execute();
+       }
+
+       return serve(port);
+}
diff --git a/date.c b/date.c
new file mode 100644 (file)
index 0000000..365dc3b
--- /dev/null
+++ b/date.c
@@ -0,0 +1,700 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include <time.h>
+#include <sys/time.h>
+
+#include "cache.h"
+
+static time_t my_mktime(struct tm *tm)
+{
+       static const int mdays[] = {
+           0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+       };
+       int year = tm->tm_year - 70;
+       int month = tm->tm_mon;
+       int day = tm->tm_mday;
+
+       if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+               return -1;
+       if (month < 0 || month > 11) /* array bounds */
+               return -1;
+       if (month < 2 || (year + 2) % 4)
+               day--;
+       return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+               tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+       "January", "February", "March", "April", "May", "June",
+       "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+       "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+/*
+ * The "tz" thing is passed in as this strange "decimal parse of tz"
+ * thing, which means that tz -0100 is passed in as the integer -100,
+ * even though it means "sixty minutes off"
+ */
+static struct tm *time_to_tm(unsigned long time, int tz)
+{
+       time_t t;
+       int minutes;
+
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       return gmtime(&t);
+}
+
+const char *show_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
+               weekday_names[tm->tm_wday],
+               month_names[tm->tm_mon],
+               tm->tm_mday,
+               tm->tm_hour, tm->tm_min, tm->tm_sec,
+               tm->tm_year + 1900, tz);
+       return timebuf;
+}
+
+const char *show_rfc2822_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+               weekday_names[tm->tm_wday], tm->tm_mday,
+               month_names[tm->tm_mon], tm->tm_year + 1900,
+               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
+       return timebuf;
+}
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+       const char *name;
+       int offset;
+       int dst;
+} timezone_names[] = {
+       { "IDLW", -12, 0, },    /* International Date Line West */
+       { "NT",   -11, 0, },    /* Nome */
+       { "CAT",  -10, 0, },    /* Central Alaska */
+       { "HST",  -10, 0, },    /* Hawaii Standard */
+       { "HDT",  -10, 1, },    /* Hawaii Daylight */
+       { "YST",   -9, 0, },    /* Yukon Standard */
+       { "YDT",   -9, 1, },    /* Yukon Daylight */
+       { "PST",   -8, 0, },    /* Pacific Standard */
+       { "PDT",   -8, 1, },    /* Pacific Daylight */
+       { "MST",   -7, 0, },    /* Mountain Standard */
+       { "MDT",   -7, 1, },    /* Mountain Daylight */
+       { "CST",   -6, 0, },    /* Central Standard */
+       { "CDT",   -6, 1, },    /* Central Daylight */
+       { "EST",   -5, 0, },    /* Eastern Standard */
+       { "EDT",   -5, 1, },    /* Eastern Daylight */
+       { "AST",   -3, 0, },    /* Atlantic Standard */
+       { "ADT",   -3, 1, },    /* Atlantic Daylight */
+       { "WAT",   -1, 0, },    /* West Africa */
+
+       { "GMT",    0, 0, },    /* Greenwich Mean */
+       { "UTC",    0, 0, },    /* Universal (Coordinated) */
+
+       { "WET",    0, 0, },    /* Western European */
+       { "BST",    0, 1, },    /* British Summer */
+       { "CET",   +1, 0, },    /* Central European */
+       { "MET",   +1, 0, },    /* Middle European */
+       { "MEWT",  +1, 0, },    /* Middle European Winter */
+       { "MEST",  +1, 1, },    /* Middle European Summer */
+       { "CEST",  +1, 1, },    /* Central European Summer */
+       { "MESZ",  +1, 1, },    /* Middle European Summer */
+       { "FWT",   +1, 0, },    /* French Winter */
+       { "FST",   +1, 1, },    /* French Summer */
+       { "EET",   +2, 0, },    /* Eastern Europe, USSR Zone 1 */
+       { "EEST",  +2, 1, },    /* Eastern European Daylight */
+       { "WAST",  +7, 0, },    /* West Australian Standard */
+       { "WADT",  +7, 1, },    /* West Australian Daylight */
+       { "CCT",   +8, 0, },    /* China Coast, USSR Zone 7 */
+       { "JST",   +9, 0, },    /* Japan Standard, USSR Zone 8 */
+       { "EAST", +10, 0, },    /* Eastern Australian Standard */
+       { "EADT", +10, 1, },    /* Eastern Australian Daylight */
+       { "GST",  +10, 0, },    /* Guam Standard, USSR Zone 9 */
+       { "NZT",  +11, 0, },    /* New Zealand */
+       { "NZST", +11, 0, },    /* New Zealand Standard */
+       { "NZDT", +11, 1, },    /* New Zealand Daylight */
+       { "IDLE", +12, 0, },    /* International Date Line East */
+};
+
+static int match_string(const char *date, const char *str)
+{
+       int i = 0;
+
+       for (i = 0; *date; date++, str++, i++) {
+               if (*date == *str)
+                       continue;
+               if (toupper(*date) == toupper(*str))
+                       continue;
+               if (!isalnum(*date))
+                       break;
+               return 0;
+       }
+       return i;
+}
+
+static int skip_alpha(const char *date)
+{
+       int i = 0;
+       do {
+               i++;
+       } while (isalpha(date[i]));
+       return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static int match_alpha(const char *date, struct tm *tm, int *offset)
+{
+       int i;
+
+       for (i = 0; i < 12; i++) {
+               int match = match_string(date, month_names[i]);
+               if (match >= 3) {
+                       tm->tm_mon = i;
+                       return match;
+               }
+       }
+
+       for (i = 0; i < 7; i++) {
+               int match = match_string(date, weekday_names[i]);
+               if (match >= 3) {
+                       tm->tm_wday = i;
+                       return match;
+               }
+       }
+
+       for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
+               int match = match_string(date, timezone_names[i].name);
+               if (match >= 3) {
+                       int off = timezone_names[i].offset;
+
+                       /* This is bogus, but we like summer */
+                       off += timezone_names[i].dst;
+
+                       /* Only use the tz name offset if we don't have anything better */
+                       if (*offset == -1)
+                               *offset = 60*off;
+
+                       return match;
+               }
+       }
+
+       if (match_string(date, "PM") == 2) {
+               if (tm->tm_hour > 0 && tm->tm_hour < 12)
+                       tm->tm_hour += 12;
+               return 2;
+       }
+
+       /* BAD CRAP */
+       return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
+{
+       if (month > 0 && month < 13 && day > 0 && day < 32) {
+               struct tm check = *tm;
+               struct tm *r = (now_tm ? &check : tm);
+               time_t specified;
+
+               r->tm_mon = month - 1;
+               r->tm_mday = day;
+               if (year == -1) {
+                       if (!now_tm)
+                               return 1;
+                       r->tm_year = now_tm->tm_year;
+               }
+               else if (year >= 1970 && year < 2100)
+                       r->tm_year = year - 1900;
+               else if (year > 70 && year < 100)
+                       r->tm_year = year;
+               else if (year < 38)
+                       r->tm_year = year + 100;
+               else
+                       return 0;
+               if (!now_tm)
+                       return 1;
+
+               specified = my_mktime(r);
+
+               /* Be it commit time or author time, it does not make
+                * sense to specify timestamp way into the future.  Make
+                * sure it is not later than ten days from now...
+                */
+               if (now + 10*24*3600 < specified)
+                       return 0;
+               tm->tm_mon = r->tm_mon;
+               tm->tm_mday = r->tm_mday;
+               if (year != -1)
+                       tm->tm_year = r->tm_year;
+               return 1;
+       }
+       return 0;
+}
+
+static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+       time_t now;
+       struct tm now_tm;
+       struct tm *refuse_future;
+       long num2, num3;
+
+       num2 = strtol(end+1, &end, 10);
+       num3 = -1;
+       if (*end == c && isdigit(end[1]))
+               num3 = strtol(end+1, &end, 10);
+
+       /* Time? Date? */
+       switch (c) {
+       case ':':
+               if (num3 < 0)
+                       num3 = 0;
+               if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+                       tm->tm_hour = num;
+                       tm->tm_min = num2;
+                       tm->tm_sec = num3;
+                       break;
+               }
+               return 0;
+
+       case '-':
+       case '/':
+       case '.':
+               now = time(NULL);
+               refuse_future = NULL;
+               if (gmtime_r(&now, &now_tm))
+                       refuse_future = &now_tm;
+
+               if (num > 70) {
+                       /* yyyy-mm-dd? */
+                       if (is_date(num, num2, num3, refuse_future, now, tm))
+                               break;
+                       /* yyyy-dd-mm? */
+                       if (is_date(num, num3, num2, refuse_future, now, tm))
+                               break;
+               }
+               /* Our eastern European friends say dd.mm.yy[yy]
+                * is the norm there, so giving precedence to
+                * mm/dd/yy[yy] form only when separator is not '.'
+                */
+               if (c != '.' &&
+                   is_date(num3, num, num2, refuse_future, now, tm))
+                       break;
+               /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
+               if (is_date(num3, num2, num, refuse_future, now, tm))
+                       break;
+               /* Funny European mm.dd.yy */
+               if (c == '.' &&
+                   is_date(num3, num, num2, refuse_future, now, tm))
+                       break;
+               return 0;
+       }
+       return end - date;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date? 
+ */
+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+       int n;
+       char *end;
+       unsigned long num;
+
+       num = strtoul(date, &end, 10);
+
+       /*
+        * Seconds since 1970? We trigger on that for anything after Jan 1, 2000
+        */
+       if (num > 946684800) {
+               time_t time = num;
+               if (gmtime_r(&time, tm)) {
+                       *tm_gmt = 1;
+                       return end - date;
+               }
+       }
+
+       /*
+        * Check for special formats: num[-.:/]num[same]num
+        */
+       switch (*end) {
+       case ':':
+       case '.':
+       case '/':
+       case '-':
+               if (isdigit(end[1])) {
+                       int match = match_multi_number(num, *end, date, end, tm);
+                       if (match)
+                               return match;
+               }
+       }
+
+       /*
+        * None of the special formats? Try to guess what
+        * the number meant. We use the number of digits
+        * to make a more educated guess..
+        */
+       n = 0;
+       do {
+               n++;
+       } while (isdigit(date[n]));
+
+       /* Four-digit year or a timezone? */
+       if (n == 4) {
+               if (num <= 1200 && *offset == -1) {
+                       unsigned int minutes = num % 100;
+                       unsigned int hours = num / 100;
+                       *offset = hours*60 + minutes;
+               } else if (num > 1900 && num < 2100)
+                       tm->tm_year = num - 1900;
+               return n;
+       }
+
+       /*
+        * NOTE! We will give precedence to day-of-month over month or
+        * year numbers in the 1-12 range. So 05 is always "mday 5",
+        * unless we already have a mday..
+        *
+        * IOW, 01 Apr 05 parses as "April 1st, 2005".
+        */
+       if (num > 0 && num < 32 && tm->tm_mday < 0) {
+               tm->tm_mday = num;
+               return n;
+       }
+
+       /* Two-digit year? */
+       if (n == 2 && tm->tm_year < 0) {
+               if (num < 10 && tm->tm_mday >= 0) {
+                       tm->tm_year = num + 100;
+                       return n;
+               }
+               if (num >= 70) {
+                       tm->tm_year = num;
+                       return n;
+               }
+       }
+
+       if (num > 0 && num < 32) {
+               tm->tm_mday = num;
+       } else if (num > 1900) {
+               tm->tm_year = num - 1900;
+       } else if (num > 70) {
+               tm->tm_year = num;
+       } else if (num > 0 && num < 13) {
+               tm->tm_mon = num-1;
+       }
+               
+       return n;
+}
+
+static int match_tz(const char *date, int *offp)
+{
+       char *end;
+       int offset = strtoul(date+1, &end, 10);
+       int min, hour;
+       int n = end - date - 1;
+
+       min = offset % 100;
+       hour = offset / 100;
+
+       /*
+        * Don't accept any random crap.. At least 3 digits, and
+        * a valid minute. We might want to check that the minutes
+        * are divisible by 30 or something too.
+        */
+       if (min < 60 && n > 2) {
+               offset = hour*60+min;
+               if (*date == '-')
+                       offset = -offset;
+
+               *offp = offset;
+       }
+       return end - date;
+}
+
+static int date_string(unsigned long date, int offset, char *buf, int len)
+{
+       int sign = '+';
+
+       if (offset < 0) {
+               offset = -offset;
+               sign = '-';
+       }
+       return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+   (i.e. English) day/month names, and it doesn't work correctly with %z. */
+int parse_date(const char *date, char *result, int maxlen)
+{
+       struct tm tm;
+       int offset, tm_gmt;
+       time_t then;
+
+       memset(&tm, 0, sizeof(tm));
+       tm.tm_year = -1;
+       tm.tm_mon = -1;
+       tm.tm_mday = -1;
+       tm.tm_isdst = -1;
+       offset = -1;
+       tm_gmt = 0;
+
+       for (;;) {
+               int match = 0;
+               unsigned char c = *date;
+
+               /* Stop at end of string or newline */
+               if (!c || c == '\n')
+                       break;
+
+               if (isalpha(c))
+                       match = match_alpha(date, &tm, &offset);
+               else if (isdigit(c))
+                       match = match_digit(date, &tm, &offset, &tm_gmt);
+               else if ((c == '-' || c == '+') && isdigit(date[1]))
+                       match = match_tz(date, &offset);
+
+               if (!match) {
+                       /* BAD CRAP */
+                       match = 1;
+               }       
+
+               date += match;
+       }
+
+       /* mktime uses local timezone */
+       then = my_mktime(&tm); 
+       if (offset == -1)
+               offset = (then - mktime(&tm)) / 60;
+
+       if (then == -1)
+               return -1;
+
+       if (!tm_gmt)
+               then -= offset * 60;
+       return date_string(then, offset, result, maxlen);
+}
+
+void datestamp(char *buf, int bufsize)
+{
+       time_t now;
+       int offset;
+
+       time(&now);
+
+       offset = my_mktime(localtime(&now)) - now;
+       offset /= 60;
+
+       date_string(now, offset, buf, bufsize);
+}
+
+static void update_tm(struct tm *tm, unsigned long sec)
+{
+       time_t n = mktime(tm) - sec;
+       localtime_r(&n, tm);
+}
+
+static void date_yesterday(struct tm *tm, int *num)
+{
+       update_tm(tm, 24*60*60);
+}
+
+static void date_time(struct tm *tm, int hour)
+{
+       if (tm->tm_hour < hour)
+               date_yesterday(tm, NULL);
+       tm->tm_hour = hour;
+       tm->tm_min = 0;
+       tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, int *num)
+{
+       date_time(tm, 0);
+}
+
+static void date_noon(struct tm *tm, int *num)
+{
+       date_time(tm, 12);
+}
+
+static void date_tea(struct tm *tm, int *num)
+{
+       date_time(tm, 17);
+}
+
+static const struct special {
+       const char *name;
+       void (*fn)(struct tm *, int *);
+} special[] = {
+       { "yesterday", date_yesterday },
+       { "noon", date_noon },
+       { "midnight", date_midnight },
+       { "tea", date_tea },
+       { NULL }
+};
+
+static const char *number_name[] = {
+       "zero", "one", "two", "three", "four",
+       "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+       const char *type;
+       int length;
+} typelen[] = {
+       { "seconds", 1 },
+       { "minutes", 60 },
+       { "hours", 60*60 },
+       { "days", 24*60*60 },
+       { "weeks", 7*24*60*60 },
+       { NULL }
+};     
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
+{
+       const struct typelen *tl;
+       const struct special *s;
+       const char *end = date;
+       int n = 1, i;
+
+       while (isalpha(*++end))
+               n++;
+
+       for (i = 0; i < 12; i++) {
+               int match = match_string(date, month_names[i]);
+               if (match >= 3) {
+                       tm->tm_mon = i;
+                       return end;
+               }
+       }
+
+       for (s = special; s->name; s++) {
+               int len = strlen(s->name);
+               if (match_string(date, s->name) == len) {
+                       s->fn(tm, num);
+                       return end;
+               }
+       }
+
+       if (!*num) {
+               for (i = 1; i < 11; i++) {
+                       int len = strlen(number_name[i]);
+                       if (match_string(date, number_name[i]) == len) {
+                               *num = i;
+                               return end;
+                       }
+               }
+               if (match_string(date, "last") == 4)
+                       *num = 1;
+               return end;
+       }
+
+       tl = typelen;
+       while (tl->type) {
+               int len = strlen(tl->type);
+               if (match_string(date, tl->type) >= len-1) {
+                       update_tm(tm, tl->length * *num);
+                       *num = 0;
+                       return end;
+               }
+               tl++;
+       }
+
+       for (i = 0; i < 7; i++) {
+               int match = match_string(date, weekday_names[i]);
+               if (match >= 3) {
+                       int diff, n = *num -1;
+                       *num = 0;
+
+                       diff = tm->tm_wday - i;
+                       if (diff <= 0)
+                               n++;
+                       diff += 7*n;
+
+                       update_tm(tm, diff * 24 * 60 * 60);
+                       return end;
+               }
+       }
+
+       if (match_string(date, "months") >= 5) {
+               int n = tm->tm_mon - *num;
+               *num = 0;
+               while (n < 0) {
+                       n += 12;
+                       tm->tm_year--;
+               }
+               tm->tm_mon = n;
+               return end;
+       }
+
+       if (match_string(date, "years") >= 4) {
+               tm->tm_year -= *num;
+               *num = 0;
+               return end;
+       }
+
+       return end;
+}
+
+unsigned long approxidate(const char *date)
+{
+       int number = 0;
+       struct tm tm, now;
+       struct timeval tv;
+       char buffer[50];
+
+       if (parse_date(date, buffer, sizeof(buffer)) > 0)
+               return strtoul(buffer, NULL, 10);
+
+       gettimeofday(&tv, NULL);
+       localtime_r(&tv.tv_sec, &tm);
+       now = tm;
+       for (;;) {
+               unsigned char c = *date;
+               if (!c)
+                       break;
+               date++;
+               if (isdigit(c)) {
+                       char *end;
+                       number = strtoul(date-1, &end, 10);
+                       date = end;
+                       continue;
+               }
+               if (isalpha(c))
+                       date = approxidate_alpha(date-1, &tm, &number);
+       }
+       if (number > 0 && number < 32)
+               tm.tm_mday = number;
+       if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year)
+               tm.tm_year--;
+       return mktime(&tm);
+}
diff --git a/delta.h b/delta.h
new file mode 100644 (file)
index 0000000..7b3f86d
--- /dev/null
+++ b/delta.h
@@ -0,0 +1,98 @@
+#ifndef DELTA_H
+#define DELTA_H
+
+/* opaque object for delta index */
+struct delta_index;
+
+/*
+ * create_delta_index: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index().  A NULL pointer
+ * is returned on failure.  The given buffer must not be freed nor altered
+ * before free_delta_index() is called.  The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern struct delta_index *
+create_delta_index(const void *buf, unsigned long bufsize);
+
+/*
+ * free_delta_index: free the index created by create_delta_index()
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern void free_delta_index(struct delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer.  If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size.  The returned buffer
+ * must be freed by the caller.
+ */
+extern void *
+create_delta(const struct delta_index *index,
+            const void *buf, unsigned long bufsize,
+            unsigned long *delta_size, unsigned long max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then NULL is returned.  On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size.  The returned buffer must be freed by the caller.
+ */
+static inline void *
+diff_delta(const void *src_buf, unsigned long src_bufsize,
+          const void *trg_buf, unsigned long trg_bufsize,
+          unsigned long *delta_size, unsigned long max_delta_size)
+{
+       struct delta_index *index = create_delta_index(src_buf, src_bufsize);
+       if (index) {
+               void *delta = create_delta(index, trg_buf, trg_bufsize,
+                                          delta_size, max_delta_size);
+               free_delta_index(index);
+               return delta;
+       }
+       return NULL;
+}
+
+/*
+ * patch_delta: recreate target buffer given source buffer and delta data
+ *
+ * On success, a non-NULL pointer to the target buffer is returned and
+ * *trg_bufsize is updated with its size.  On failure a NULL pointer is
+ * returned.  The returned buffer must be freed by the caller.
+ */
+extern void *patch_delta(const void *src_buf, unsigned long src_size,
+                        const void *delta_buf, unsigned long delta_size,
+                        unsigned long *dst_size);
+
+/* the smallest possible delta size is 4 bytes */
+#define DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected source buffer size, and again to get the target buffer size.
+ */
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
+                                              const unsigned char *top)
+{
+       const unsigned char *data = *datap;
+       unsigned char cmd;
+       unsigned long size = 0;
+       int i = 0;
+       do {
+               cmd = *data++;
+               size |= (cmd & ~0x80) << i;
+               i += 7;
+       } while (cmd & 0x80 && data < top);
+       *datap = data;
+       return size;
+}
+
+#endif
diff --git a/describe.c b/describe.c
new file mode 100644 (file)
index 0000000..8a9cd5d
--- /dev/null
@@ -0,0 +1,173 @@
+#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 = 0;    /* Default to annotated tags only */
+static int tags = 0;   /* But allow any tags if --tags is specified */
+
+static int abbrev = DEFAULT_ABBREV;
+
+static int names = 0, allocs = 0;
+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)
+{
+       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 == tag_type)
+                       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(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);
+               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);
+       }
+
+       if (i == argc)
+               describe("HEAD", 1);
+       else
+               while (i < argc) {
+                       describe(argv[i], (i == argc - 1));
+                       i++;
+               }
+
+       return 0;
+}
diff --git a/diff-delta.c b/diff-delta.c
new file mode 100644 (file)
index 0000000..25a798d
--- /dev/null
@@ -0,0 +1,412 @@
+/*
+ * diff-delta.c: generate a delta between two buffers
+ *
+ *  Many parts of this file have been lifted from LibXDiff version 0.10.
+ *  http://www.xmailserver.org/xdiff-lib.html
+ *
+ *  LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
+ *
+ *  This file is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  Use of this within git automatically means that the LGPL
+ *  licensing gets turned into GPLv2 within this project.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "delta.h"
+
+
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+       0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+       0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+       0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+       0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+       0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+       0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+       0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+       0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+       0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+       0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+       0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+       0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+       0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+       0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+       0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+       0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+       0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+       0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+       0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+       0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+       0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+       0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+       0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+       0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+       0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+       0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+       0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+       0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+       0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+       0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+       0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+       0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+       0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+       0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+       0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+       0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+       0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+       0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+       0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+       0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+       0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+       0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+       0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
+
+static const unsigned int U[256] = {
+       0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+       0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+       0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+       0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+       0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+       0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+       0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+       0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+       0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+       0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+       0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+       0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+       0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+       0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+       0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+       0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+       0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+       0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+       0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+       0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+       0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+       0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+       0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+       0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+       0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+       0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+       0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+       0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+       0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+       0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+       0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+       0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+       0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+       0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+       0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+       0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+       0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+       0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+       0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+       0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+       0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+       0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+       0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
+
+struct index_entry {
+       const unsigned char *ptr;
+       unsigned int val;
+       struct index_entry *next;
+};
+
+struct delta_index {
+       const void *src_buf;
+       unsigned long src_size;
+       unsigned int hash_mask;
+       struct index_entry *hash[0];
+};
+
+struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
+{
+       unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+       const unsigned char *data, *buffer = buf;
+       struct delta_index *index;
+       struct index_entry *entry, **hash;
+       void *mem;
+       unsigned long memsize;
+
+       if (!buf || !bufsize)
+               return NULL;
+
+       /* Determine index hash size.  Note that indexing skips the
+          first byte to allow for optimizing the rabin polynomial
+          initialization in create_delta(). */
+       entries = (bufsize - 1)  / RABIN_WINDOW;
+       hsize = entries / 4;
+       for (i = 4; (1 << i) < hsize && i < 31; i++);
+       hsize = 1 << i;
+       hmask = hsize - 1;
+
+       /* allocate lookup index */
+       memsize = sizeof(*index) +
+                 sizeof(*hash) * hsize +
+                 sizeof(*entry) * entries;
+       mem = malloc(memsize);
+       if (!mem)
+               return NULL;
+       index = mem;
+       mem = index + 1;
+       hash = mem;
+       mem = hash + hsize;
+       entry = mem;
+
+       index->src_buf = buf;
+       index->src_size = bufsize;
+       index->hash_mask = hmask;
+       memset(hash, 0, hsize * sizeof(*hash));
+
+       /* allocate an array to count hash entries */
+       hash_count = calloc(hsize, sizeof(*hash_count));
+       if (!hash_count) {
+               free(index);
+               return NULL;
+       }
+
+       /* then populate the index */
+       prev_val = ~0;
+       for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+            data >= buffer;
+            data -= RABIN_WINDOW) {
+               unsigned int val = 0;
+               for (i = 1; i <= RABIN_WINDOW; i++)
+                       val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+               if (val == prev_val) {
+                       /* keep the lowest of consecutive identical blocks */
+                       entry[-1].ptr = data + RABIN_WINDOW;
+               } else {
+                       prev_val = val;
+                       i = val & hmask;
+                       entry->ptr = data + RABIN_WINDOW;
+                       entry->val = val;
+                       entry->next = hash[i];
+                       hash[i] = entry++;
+                       hash_count[i]++;
+               }
+       }
+
+       /*
+        * Determine a limit on the number of entries in the same hash
+        * bucket.  This guard us against patological data sets causing
+        * really bad hash distribution with most entries in the same hash
+        * bucket that would bring us to O(m*n) computing costs (m and n
+        * corresponding to reference and target buffer sizes).
+        *
+        * Make sure none of the hash buckets has more entries than
+        * we're willing to test.  Otherwise we cull the entry list
+        * uniformly to still preserve a good repartition across
+        * the reference buffer.
+        */
+       for (i = 0; i < hsize; i++) {
+               if (hash_count[i] < HASH_LIMIT)
+                       continue;
+               entry = hash[i];
+               do {
+                       struct index_entry *keep = entry;
+                       int skip = hash_count[i] / HASH_LIMIT / 2;
+                       do {
+                               entry = entry->next;
+                       } while(--skip && entry);
+                       keep->next = entry;
+               } while(entry);
+       }
+       free(hash_count);
+
+       return index;
+}
+
+void free_delta_index(struct delta_index *index)
+{
+       free(index);
+}
+
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE    (5 + 5 + 1 + RABIN_WINDOW + 7)
+
+void *
+create_delta(const struct delta_index *index,
+            const void *trg_buf, unsigned long trg_size,
+            unsigned long *delta_size, unsigned long max_size)
+{
+       unsigned int i, outpos, outsize, val;
+       int inscnt;
+       const unsigned char *ref_data, *ref_top, *data, *top;
+       unsigned char *out;
+
+       if (!trg_buf || !trg_size)
+               return NULL;
+
+       outpos = 0;
+       outsize = 8192;
+       if (max_size && outsize >= max_size)
+               outsize = max_size + MAX_OP_SIZE + 1;
+       out = malloc(outsize);
+       if (!out)
+               return NULL;
+
+       /* store reference buffer size */
+       i = index->src_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
+       }
+       out[outpos++] = i;
+
+       /* store target buffer size */
+       i = trg_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
+       }
+       out[outpos++] = i;
+
+       ref_data = index->src_buf;
+       ref_top = ref_data + index->src_size;
+       data = trg_buf;
+       top = trg_buf + trg_size;
+
+       outpos++;
+       val = 0;
+       for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+               out[outpos++] = *data;
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+       }
+       inscnt = i;
+
+       while (data < top) {
+               unsigned int moff = 0, msize = 0;
+               struct index_entry *entry;
+               val ^= U[data[-RABIN_WINDOW]];
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+               i = val & index->hash_mask;
+               for (entry = index->hash[i]; entry; entry = entry->next) {
+                       const unsigned char *ref = entry->ptr;
+                       const unsigned char *src = data;
+                       unsigned int ref_size = ref_top - ref;
+                       if (entry->val != val)
+                               continue;
+                       if (ref_size > top - src)
+                               ref_size = top - src;
+                       if (ref_size > 0x10000)
+                               ref_size = 0x10000;
+                       if (ref_size <= msize)
+                               break;
+                       while (ref_size-- && *src++ == *ref)
+                               ref++;
+                       if (msize < ref - entry->ptr) {
+                               /* this is our best match so far */
+                               msize = ref - entry->ptr;
+                               moff = entry->ptr - ref_data;
+                       }
+               }
+
+               if (msize < 4) {
+                       if (!inscnt)
+                               outpos++;
+                       out[outpos++] = *data++;
+                       inscnt++;
+                       if (inscnt == 0x7f) {
+                               out[outpos - inscnt - 1] = inscnt;
+                               inscnt = 0;
+                       }
+               } else {
+                       unsigned char *op;
+
+                       if (msize >= RABIN_WINDOW) {
+                               const unsigned char *sk;
+                               sk = data + msize - RABIN_WINDOW;
+                               val = 0;
+                               for (i = 0; i < RABIN_WINDOW; i++)
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                       } else {
+                               const unsigned char *sk = data + 1;
+                               for (i = 1; i < msize; i++) {
+                                       val ^= U[sk[-RABIN_WINDOW]];
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                               }
+                       }
+
+                       if (inscnt) {
+                               while (moff && ref_data[moff-1] == data[-1]) {
+                                       if (msize == 0x10000)
+                                               break;
+                                       /* we can match one byte back */
+                                       msize++;
+                                       moff--;
+                                       data--;
+                                       outpos--;
+                                       if (--inscnt)
+                                               continue;
+                                       outpos--;  /* remove count slot */
+                                       inscnt--;  /* make it -1 */
+                                       break;
+                               }
+                               out[outpos - inscnt - 1] = inscnt;
+                               inscnt = 0;
+                       }
+
+                       data += msize;
+                       op = out + outpos++;
+                       i = 0x80;
+
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
+                       moff >>= 8;
+                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
+
+                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
+                       msize >>= 8;
+                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+
+                       *op = i;
+               }
+
+               if (outpos >= outsize - MAX_OP_SIZE) {
+                       void *tmp = out;
+                       outsize = outsize * 3 / 2;
+                       if (max_size && outsize >= max_size)
+                               outsize = max_size + MAX_OP_SIZE + 1;
+                       if (max_size && outpos > max_size)
+                               break;
+                       out = realloc(out, outsize);
+                       if (!out) {
+                               free(tmp);
+                               return NULL;
+                       }
+               }
+       }
+
+       if (inscnt)
+               out[outpos - inscnt - 1] = inscnt;
+
+       if (max_size && outpos > max_size) {
+               free(out);
+               return NULL;
+       }
+
+       *delta_size = outpos;
+       return out;
+}
diff --git a/diff-lib.c b/diff-lib.c
new file mode 100644 (file)
index 0000000..2183b41
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "quote.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+/*
+ * diff-files
+ */
+
+int run_diff_files(struct rev_info *revs, int silent_on_removed)
+{
+       int entries, i;
+       int diff_unmerged_stage = revs->max_count;
+
+       if (diff_unmerged_stage < 0)
+               diff_unmerged_stage = 2;
+       entries = read_cache();
+       if (entries < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       for (i = 0; i < entries; i++) {
+               struct stat st;
+               unsigned int oldmode, newmode;
+               struct cache_entry *ce = active_cache[i];
+               int changed;
+
+               if (!ce_path_match(ce, revs->prune_data))
+                       continue;
+
+               if (ce_stage(ce)) {
+                       struct {
+                               struct combine_diff_path p;
+                               struct combine_diff_parent filler[5];
+                       } combine;
+                       int num_compare_stages = 0;
+
+                       combine.p.next = NULL;
+                       combine.p.len = ce_namelen(ce);
+                       combine.p.path = xmalloc(combine.p.len + 1);
+                       memcpy(combine.p.path, ce->name, combine.p.len);
+                       combine.p.path[combine.p.len] = 0;
+                       combine.p.mode = 0;
+                       memset(combine.p.sha1, 0, 20);
+                       memset(&combine.p.parent[0], 0,
+                              sizeof(combine.filler));
+
+                       while (i < entries) {
+                               struct cache_entry *nce = active_cache[i];
+                               int stage;
+
+                               if (strcmp(ce->name, nce->name))
+                                       break;
+
+                               /* Stage #2 (ours) is the first parent,
+                                * stage #3 (theirs) is the second.
+                                */
+                               stage = ce_stage(nce);
+                               if (2 <= stage) {
+                                       int mode = ntohl(nce->ce_mode);
+                                       num_compare_stages++;
+                                       memcpy(combine.p.parent[stage-2].sha1,
+                                              nce->sha1, 20);
+                                       combine.p.parent[stage-2].mode =
+                                               canon_mode(mode);
+                                       combine.p.parent[stage-2].status =
+                                               DIFF_STATUS_MODIFIED;
+                               }
+
+                               /* diff against the proper unmerged stage */
+                               if (stage == diff_unmerged_stage)
+                                       ce = nce;
+                               i++;
+                       }
+                       /*
+                        * Compensate for loop update
+                        */
+                       i--;
+
+                       if (revs->combine_merges && num_compare_stages == 2) {
+                               show_combined_diff(&combine.p, 2,
+                                                  revs->dense_combined_merges,
+                                                  revs);
+                               free(combine.p.path);
+                               continue;
+                       }
+                       free(combine.p.path);
+
+                       /*
+                        * Show the diff for the 'ce' if we found the one
+                        * from the desired stage.
+                        */
+                       diff_unmerge(&revs->diffopt, ce->name);
+                       if (ce_stage(ce) != diff_unmerged_stage)
+                               continue;
+               }
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT && errno != ENOTDIR) {
+                               perror(ce->name);
+                               continue;
+                       }
+                       if (silent_on_removed)
+                               continue;
+                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+                                      ce->sha1, ce->name, NULL);
+                       continue;
+               }
+               changed = ce_match_stat(ce, &st, 0);
+               if (!changed && !revs->diffopt.find_copies_harder)
+                       continue;
+               oldmode = ntohl(ce->ce_mode);
+
+               newmode = canon_mode(st.st_mode);
+               if (!trust_executable_bit &&
+                   S_ISREG(newmode) && S_ISREG(oldmode) &&
+                   ((newmode ^ oldmode) == 0111))
+                       newmode = oldmode;
+               diff_change(&revs->diffopt, oldmode, newmode,
+                           ce->sha1, (changed ? null_sha1 : ce->sha1),
+                           ce->name, NULL);
+
+       }
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+/*
+ * diff-index
+ */
+
+/* A file entry went away or appeared */
+static void diff_index_show_file(struct rev_info *revs,
+                                const char *prefix,
+                                struct cache_entry *ce,
+                                unsigned char *sha1, unsigned int mode)
+{
+       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+                      sha1, ce->name, NULL);
+}
+
+static int get_stat_data(struct cache_entry *ce,
+                        unsigned char **sha1p,
+                        unsigned int *modep,
+                        int cached, int match_missing)
+{
+       unsigned char *sha1 = ce->sha1;
+       unsigned int mode = ce->ce_mode;
+
+       if (!cached) {
+               static unsigned char no_sha1[20];
+               int changed;
+               struct stat st;
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno == ENOENT && match_missing) {
+                               *sha1p = sha1;
+                               *modep = mode;
+                               return 0;
+                       }
+                       return -1;
+               }
+               changed = ce_match_stat(ce, &st, 0);
+               if (changed) {
+                       mode = create_ce_mode(st.st_mode);
+                       if (!trust_executable_bit && S_ISREG(st.st_mode))
+                               mode = ce->ce_mode;
+                       sha1 = no_sha1;
+               }
+       }
+
+       *sha1p = sha1;
+       *modep = mode;
+       return 0;
+}
+
+static void show_new_file(struct rev_info *revs,
+                         struct cache_entry *new,
+                         int cached, int match_missing)
+{
+       unsigned char *sha1;
+       unsigned int mode;
+
+       /* New file in the index: it might actually be different in
+        * the working copy.
+        */
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+               return;
+
+       diff_index_show_file(revs, "+", new, sha1, mode);
+}
+
+static int show_modified(struct rev_info *revs,
+                        struct cache_entry *old,
+                        struct cache_entry *new,
+                        int report_missing,
+                        int cached, int match_missing)
+{
+       unsigned int mode, oldmode;
+       unsigned char *sha1;
+
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+               if (report_missing)
+                       diff_index_show_file(revs, "-", old,
+                                            old->sha1, old->ce_mode);
+               return -1;
+       }
+
+       oldmode = old->ce_mode;
+       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+           !revs->diffopt.find_copies_harder)
+               return 0;
+
+       mode = ntohl(mode);
+       oldmode = ntohl(oldmode);
+
+       diff_change(&revs->diffopt, oldmode, mode,
+                   old->sha1, sha1, old->name, NULL);
+       return 0;
+}
+
+static int diff_cache(struct rev_info *revs,
+                     struct cache_entry **ac, int entries,
+                     const char **pathspec,
+                     int cached, int match_missing)
+{
+       while (entries) {
+               struct cache_entry *ce = *ac;
+               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+               if (!ce_path_match(ce, pathspec))
+                       goto skip_entry;
+
+               switch (ce_stage(ce)) {
+               case 0:
+                       /* No stage 1 entry? That means it's a new file */
+                       if (!same) {
+                               show_new_file(revs, ce, cached, match_missing);
+                               break;
+                       }
+                       /* Show difference between old and new */
+                       show_modified(revs,ac[1], ce, 1,
+                                     cached, match_missing);
+                       break;
+               case 1:
+                       /* No stage 3 (merge) entry?
+                        * That means it's been deleted.
+                        */
+                       if (!same) {
+                               diff_index_show_file(revs, "-", ce,
+                                                    ce->sha1, ce->ce_mode);
+                               break;
+                       }
+                       /* We come here with ce pointing at stage 1
+                        * (original tree) and ac[1] pointing at stage
+                        * 3 (unmerged).  show-modified with
+                        * report-missing set to false does not say the
+                        * file is deleted but reports true if work
+                        * tree does not have it, in which case we
+                        * fall through to report the unmerged state.
+                        * Otherwise, we show the differences between
+                        * the original tree and the work tree.
+                        */
+                       if (!cached &&
+                           !show_modified(revs, ce, ac[1], 0,
+                                          cached, match_missing))
+                               break;
+                       /* fallthru */
+               case 3:
+                       diff_unmerge(&revs->diffopt, ce->name);
+                       break;
+
+               default:
+                       die("impossible cache entry stage");
+               }
+
+skip_entry:
+               /*
+                * Ignore all the different stages for this file,
+                * we've handled the relevant cases now.
+                */
+               do {
+                       ac++;
+                       entries--;
+               } while (entries && ce_same_name(ce, ac[0]));
+       }
+       return 0;
+}
+
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= htons(CE_STAGEMASK);
+       }
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+       int ret;
+       struct object *ent;
+       struct tree *tree;
+       const char *tree_name;
+       int match_missing = 0;
+
+       /* 
+        * Backward compatibility wart - "diff-index -m" does
+        * not mean "do not ignore merges", but totally different.
+        */
+       if (!revs->ignore_merges)
+               match_missing = 1;
+
+       if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       mark_merge_entries();
+
+       ent = revs->pending_objects->item;
+       tree_name = revs->pending_objects->name;
+       tree = parse_tree_indirect(ent->sha1);
+       if (!tree)
+               return error("bad tree object %s", tree_name);
+       if (read_tree(tree, 1, revs->prune_data))
+               return error("unable to read tree object %s", tree_name);
+       ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
+                        cached, match_missing);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return ret;
+}
diff --git a/diff.c b/diff.c
new file mode 100644 (file)
index 0000000..9e9cfc8
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,2172 @@
+/*
+ * 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"
+#include "diffcore.h"
+#include "delta.h"
+#include "xdiff-interface.h"
+
+static int use_size_cache;
+
+int diff_rename_limit_default = -1;
+
+int git_diff_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "diff.renamelimit")) {
+               diff_rename_limit_default = git_config_int(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+static char *quote_one(const char *str)
+{
+       int needlen;
+       char *xp;
+
+       if (!str)
+               return NULL;
+       needlen = quote_c_style(str, NULL, NULL, 0);
+       if (!needlen)
+               return strdup(str);
+       xp = xmalloc(needlen + 1);
+       quote_c_style(str, xp, NULL, 0);
+       return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+       int need_one = quote_c_style(one, NULL, NULL, 1);
+       int need_two = quote_c_style(two, NULL, NULL, 1);
+       char *xp;
+
+       if (need_one + need_two) {
+               if (!need_one) need_one = strlen(one);
+               if (!need_two) need_one = strlen(two);
+
+               xp = xmalloc(need_one + need_two + 3);
+               xp[0] = '"';
+               quote_c_style(one, xp + 1, NULL, 1);
+               quote_c_style(two, xp + need_one + 1, NULL, 1);
+               strcpy(xp + need_one + need_two + 1, "\"");
+               return xp;
+       }
+       need_one = strlen(one);
+       need_two = strlen(two);
+       xp = xmalloc(need_one + need_two + 1);
+       strcpy(xp, one);
+       strcpy(xp + need_one, two);
+       return xp;
+}
+
+static const char *external_diff(void)
+{
+       static const char *external_diff_cmd = NULL;
+       static int done_preparing = 0;
+
+       if (done_preparing)
+               return external_diff_cmd;
+       external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+       done_preparing = 1;
+       return external_diff_cmd;
+}
+
+#define TEMPFILE_PATH_LEN              50
+
+static struct diff_tempfile {
+       const char *name; /* filename external diff should read from */
+       char hex[41];
+       char mode[10];
+       char tmp_path[TEMPFILE_PATH_LEN];
+} diff_temp[2];
+
+static int count_lines(const char *data, int size)
+{
+       int count, ch, completely_empty = 1, nl_just_seen = 0;
+       count = 0;
+       while (0 < size--) {
+               ch = *data++;
+               if (ch == '\n') {
+                       count++;
+                       nl_just_seen = 1;
+                       completely_empty = 0;
+               }
+               else {
+                       nl_just_seen = 0;
+                       completely_empty = 0;
+               }
+       }
+       if (completely_empty)
+               return 0;
+       if (!nl_just_seen)
+               count++; /* no trailing newline */
+       return count;
+}
+
+static void print_line_count(int count)
+{
+       switch (count) {
+       case 0:
+               printf("0,0");
+               break;
+       case 1:
+               printf("1");
+               break;
+       default:
+               printf("1,%d", count);
+               break;
+       }
+}
+
+static void copy_file(int prefix, const char *data, int size)
+{
+       int ch, nl_just_seen = 1;
+       while (0 < size--) {
+               ch = *data++;
+               if (nl_just_seen)
+                       putchar(prefix);
+               putchar(ch);
+               if (ch == '\n')
+                       nl_just_seen = 1;
+               else
+                       nl_just_seen = 0;
+       }
+       if (!nl_just_seen)
+               printf("\n\\ No newline at end of file\n");
+}
+
+static void emit_rewrite_diff(const char *name_a,
+                             const char *name_b,
+                             struct diff_filespec *one,
+                             struct diff_filespec *two)
+{
+       int lc_a, lc_b;
+       diff_populate_filespec(one, 0);
+       diff_populate_filespec(two, 0);
+       lc_a = count_lines(one->data, one->size);
+       lc_b = count_lines(two->data, two->size);
+       printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+       print_line_count(lc_a);
+       printf(" +");
+       print_line_count(lc_b);
+       printf(" @@\n");
+       if (lc_a)
+               copy_file('-', one->data, one->size);
+       if (lc_b)
+               copy_file('+', two->data, two->size);
+}
+
+static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+               mf->ptr = ""; /* does not matter */
+               mf->size = 0;
+               return 0;
+       }
+       else if (diff_populate_filespec(one, 0))
+               return -1;
+       mf->ptr = one->data;
+       mf->size = one->size;
+       return 0;
+}
+
+struct emit_callback {
+       const char **label_path;
+};
+
+static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+       int i;
+       struct emit_callback *ecbdata = priv;
+
+       if (ecbdata->label_path[0]) {
+               printf("--- %s\n", ecbdata->label_path[0]);
+               printf("+++ %s\n", ecbdata->label_path[1]);
+               ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
+       }
+       for (i = 0; i < nbuf; i++)
+               if (!fwrite(mb[i].ptr, mb[i].size, 1, stdout))
+                       return -1;
+       return 0;
+}
+
+static char *pprint_rename(const char *a, const char *b)
+{
+       const char *old = a;
+       const char *new = b;
+       char *name = NULL;
+       int pfx_length, sfx_length;
+       int len_a = strlen(a);
+       int len_b = strlen(b);
+
+       /* Find common prefix */
+       pfx_length = 0;
+       while (*old && *new && *old == *new) {
+               if (*old == '/')
+                       pfx_length = old - a + 1;
+               old++;
+               new++;
+       }
+
+       /* Find common suffix */
+       old = a + len_a;
+       new = b + len_b;
+       sfx_length = 0;
+       while (a <= old && b <= new && *old == *new) {
+               if (*old == '/')
+                       sfx_length = len_a - (old - a);
+               old--;
+               new--;
+       }
+
+       /*
+        * pfx{mid-a => mid-b}sfx
+        * {pfx-a => pfx-b}sfx
+        * pfx{sfx-a => sfx-b}
+        * name-a => name-b
+        */
+       if (pfx_length + sfx_length) {
+               int a_midlen = len_a - pfx_length - sfx_length;
+               int b_midlen = len_b - pfx_length - sfx_length;
+               if (a_midlen < 0) a_midlen = 0;
+               if (b_midlen < 0) b_midlen = 0;
+
+               name = xmalloc(pfx_length + a_midlen + b_midlen + sfx_length + 7);
+               sprintf(name, "%.*s{%.*s => %.*s}%s",
+                       pfx_length, a,
+                       a_midlen, a + pfx_length,
+                       b_midlen, b + pfx_length,
+                       a + len_a - sfx_length);
+       }
+       else {
+               name = xmalloc(len_a + len_b + 5);
+               sprintf(name, "%s => %s", a, b);
+       }
+       return name;
+}
+
+struct diffstat_t {
+       struct xdiff_emit_state xm;
+
+       int nr;
+       int alloc;
+       struct diffstat_file {
+               char *name;
+               unsigned is_unmerged:1;
+               unsigned is_binary:1;
+               unsigned is_renamed:1;
+               unsigned int added, deleted;
+       } **files;
+};
+
+static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
+                                         const char *name_a,
+                                         const char *name_b)
+{
+       struct diffstat_file *x;
+       x = xcalloc(sizeof (*x), 1);
+       if (diffstat->nr == diffstat->alloc) {
+               diffstat->alloc = alloc_nr(diffstat->alloc);
+               diffstat->files = xrealloc(diffstat->files,
+                               diffstat->alloc * sizeof(x));
+       }
+       diffstat->files[diffstat->nr++] = x;
+       if (name_b) {
+               x->name = pprint_rename(name_a, name_b);
+               x->is_renamed = 1;
+       }
+       else
+               x->name = strdup(name_a);
+       return x;
+}
+
+static void diffstat_consume(void *priv, char *line, unsigned long len)
+{
+       struct diffstat_t *diffstat = priv;
+       struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+
+       if (line[0] == '+')
+               x->added++;
+       else if (line[0] == '-')
+               x->deleted++;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+const char mime_boundary_leader[] = "------------";
+
+static void show_stats(struct diffstat_t* data)
+{
+       int i, len, add, del, total, adds = 0, dels = 0;
+       int max, max_change = 0, max_len = 0;
+       int total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+
+               len = strlen(file->name);
+               if (max_len < len)
+                       max_len = len;
+
+               if (file->is_binary || file->is_unmerged)
+                       continue;
+               if (max_change < file->added + file->deleted)
+                       max_change = file->added + file->deleted;
+       }
+
+       for (i = 0; i < data->nr; i++) {
+               char *prefix = "";
+               char *name = data->files[i]->name;
+               int added = data->files[i]->added;
+               int deleted = data->files[i]->deleted;
+
+               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(name, qname, NULL, 0);
+                       free(name);
+                       data->files[i]->name = name = qname;
+               }
+
+               /*
+                * "scale" the filename
+                */
+               len = strlen(name);
+               max = max_len;
+               if (max > 50)
+                       max = 50;
+               if (len > max) {
+                       char *slash;
+                       prefix = "...";
+                       max -= 3;
+                       name += len - max;
+                       slash = strchr(name, '/');
+                       if (slash)
+                               name = slash;
+               }
+               len = max;
+
+               /*
+                * scale the add/delete
+                */
+               max = max_change;
+               if (max + len > 70)
+                       max = 70 - len;
+
+               if (data->files[i]->is_binary) {
+                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (data->files[i]->is_unmerged) {
+                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
+                       total_files--;
+                       goto free_diffstat_file;
+               }
+
+               add = added;
+               del = deleted;
+               total = add + del;
+               adds += add;
+               dels += del;
+
+               if (max_change > 0) {
+                       total = (total * max + max_change / 2) / max_change;
+                       add = (add * max + max_change / 2) / max_change;
+                       del = total - add;
+               }
+               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+                               len, name, added + deleted,
+                               add, pluses, del, minuses);
+       free_diffstat_file:
+               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);
+}
+
+struct checkdiff_t {
+       struct xdiff_emit_state xm;
+       const char *filename;
+       int lineno;
+};
+
+static void checkdiff_consume(void *priv, char *line, unsigned long len)
+{
+       struct checkdiff_t *data = priv;
+
+       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] == ' ')
+                               spaces++;
+               if (line[i - 1] == '\t' && spaces)
+                       printf("%s:%d: space before tab:%.*s\n",
+                               data->filename, data->lineno, (int)len, line);
+
+               /* check white space at line end */
+               if (line[len - 1] == '\n')
+                       len--;
+               if (isspace(line[len - 1]))
+                       printf("%s:%d: white space at end: %.*s\n",
+                               data->filename, data->lineno, (int)len, line);
+       } else if (line[0] == ' ')
+               data->lineno++;
+       else if (line[0] == '@') {
+               char *plus = strchr(line, '+');
+               if (plus)
+                       data->lineno = strtol(plus, NULL, 10);
+               else
+                       die("invalid diff");
+       }
+}
+
+static unsigned char *deflate_it(char *data,
+                                unsigned long size,
+                                unsigned long *result_size)
+{
+       int bound;
+       unsigned char *deflated;
+       z_stream stream;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       bound = deflateBound(&stream, size);
+       deflated = xmalloc(bound);
+       stream.next_out = deflated;
+       stream.avail_out = bound;
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       deflateEnd(&stream);
+       *result_size = stream.total_out;
+       return deflated;
+}
+
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+       void *cp;
+       void *delta;
+       void *deflated;
+       void *data;
+       unsigned long orig_size;
+       unsigned long delta_size;
+       unsigned long deflate_size;
+       unsigned long data_size;
+
+       printf("GIT binary patch\n");
+       /* We could do deflated delta, or we could do just deflated two,
+        * whichever is smaller.
+        */
+       delta = NULL;
+       deflated = deflate_it(two->ptr, two->size, &deflate_size);
+       if (one->size && two->size) {
+               delta = diff_delta(one->ptr, one->size,
+                                  two->ptr, two->size,
+                                  &delta_size, deflate_size);
+               if (delta) {
+                       void *to_free = delta;
+                       orig_size = delta_size;
+                       delta = deflate_it(delta, delta_size, &delta_size);
+                       free(to_free);
+               }
+       }
+
+       if (delta && delta_size < deflate_size) {
+               printf("delta %lu\n", orig_size);
+               free(deflated);
+               data = delta;
+               data_size = delta_size;
+       }
+       else {
+               printf("literal %lu\n", two->size);
+               free(delta);
+               data = deflated;
+               data_size = deflate_size;
+       }
+
+       /* emit data encoded in base85 */
+       cp = data;
+       while (data_size) {
+               int bytes = (52 < data_size) ? 52 : data_size;
+               char line[70];
+               data_size -= bytes;
+               if (bytes <= 26)
+                       line[0] = bytes + 'A' - 1;
+               else
+                       line[0] = bytes - 26 + 'a' - 1;
+               encode_85(line + 1, cp, bytes);
+               cp += bytes;
+               puts(line);
+       }
+       printf("\n");
+       free(data);
+}
+
+#define FIRST_FEW_BYTES 8000
+static int mmfile_is_binary(mmfile_t *mf)
+{
+       long sz = mf->size;
+       if (FIRST_FEW_BYTES < sz)
+               sz = FIRST_FEW_BYTES;
+       if (memchr(mf->ptr, 0, sz))
+               return 1;
+       return 0;
+}
+
+static void builtin_diff(const char *name_a,
+                        const char *name_b,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        struct diff_options *o,
+                        int complete_rewrite)
+{
+       mmfile_t mf1, mf2;
+       const char *lbl[2];
+       char *a_one, *b_two;
+
+       a_one = quote_two("a/", name_a);
+       b_two = quote_two("b/", name_b);
+       lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
+       lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+       printf("diff --git %s %s\n", a_one, b_two);
+       if (lbl[0][0] == '/') {
+               /* /dev/null */
+               printf("new file mode %06o\n", two->mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else if (lbl[1][0] == '/') {
+               printf("deleted file mode %06o\n", one->mode);
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+       }
+       else {
+               if (one->mode != two->mode) {
+                       printf("old mode %06o\n", one->mode);
+                       printf("new mode %06o\n", two->mode);
+               }
+               if (xfrm_msg && xfrm_msg[0])
+                       puts(xfrm_msg);
+               /*
+                * we do not run diff between different kind
+                * of objects.
+                */
+               if ((one->mode ^ two->mode) & S_IFMT)
+                       goto free_ab_and_return;
+               if (complete_rewrite) {
+                       emit_rewrite_diff(name_a, name_b, one, two);
+                       goto free_ab_and_return;
+               }
+       }
+
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+               /* Quite common confusing case */
+               if (mf1.size == mf2.size &&
+                   !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+                       goto free_ab_and_return;
+               if (o->binary)
+                       emit_binary_diff(&mf1, &mf2);
+               else
+                       printf("Binary files %s and %s differ\n",
+                              lbl[0], lbl[1]);
+       }
+       else {
+               /* Crazy xdl interfaces.. */
+               const char *diffopts = getenv("GIT_DIFF_OPTS");
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+               struct emit_callback ecbdata;
+
+               ecbdata.label_path = lbl;
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = o->context;
+               xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (!diffopts)
+                       ;
+               else if (!strncmp(diffopts, "--unified=", 10))
+                       xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
+               else if (!strncmp(diffopts, "-u", 2))
+                       xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
+               ecb.outf = fn_out;
+               ecb.priv = &ecbdata;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+
+ free_ab_and_return:
+       free(a_one);
+       free(b_two);
+       return;
+}
+
+static void builtin_diffstat(const char *name_a, const char *name_b,
+                            struct diff_filespec *one,
+                            struct diff_filespec *two,
+                            struct diffstat_t *diffstat,
+                            int complete_rewrite)
+{
+       mmfile_t mf1, mf2;
+       struct diffstat_file *data;
+
+       data = diffstat_add(diffstat, name_a, name_b);
+
+       if (!one || !two) {
+               data->is_unmerged = 1;
+               return;
+       }
+       if (complete_rewrite) {
+               diff_populate_filespec(one, 0);
+               diff_populate_filespec(two, 0);
+               data->deleted = count_lines(one->data, one->size);
+               data->added = count_lines(two->data, two->size);
+               return;
+       }
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+               data->is_binary = 1;
+       else {
+               /* Crazy xdl interfaces.. */
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 0;
+               xecfg.flags = 0;
+               ecb.outf = xdiff_outf;
+               ecb.priv = diffstat;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+}
+
+static void builtin_checkdiff(const char *name_a, const char *name_b,
+                            struct diff_filespec *one,
+                            struct diff_filespec *two)
+{
+       mmfile_t mf1, mf2;
+       struct checkdiff_t data;
+
+       if (!two)
+               return;
+
+       memset(&data, 0, sizeof(data));
+       data.xm.consume = checkdiff_consume;
+       data.filename = name_b ? name_b : name_a;
+       data.lineno = 0;
+
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf2))
+               return;
+       else {
+               /* Crazy xdl interfaces.. */
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 0;
+               xecfg.flags = 0;
+               ecb.outf = xdiff_outf;
+               ecb.priv = &data;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+}
+
+struct diff_filespec *alloc_filespec(const char *path)
+{
+       int namelen = strlen(path);
+       struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+
+       memset(spec, 0, sizeof(*spec));
+       spec->path = (char *)(spec + 1);
+       memcpy(spec->path, path, namelen+1);
+       return spec;
+}
+
+void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
+                  unsigned short mode)
+{
+       if (mode) {
+               spec->mode = canon_mode(mode);
+               memcpy(spec->sha1, sha1, 20);
+               spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+       }
+}
+
+/*
+ * Given a name and sha1 pair, if the dircache tells us the file in
+ * 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)
+{
+       struct cache_entry *ce;
+       struct stat st;
+       int pos, len;
+
+       /* We do not read the cache ourselves here, because the
+        * benchmark with my previous version that always reads cache
+        * shows that it makes things worse for diff-tree comparing
+        * two linux-2.6 kernel trees in an already checked out work
+        * tree.  This is because most diff-tree comparisons deal with
+        * only a small number of files, while reading the cache is
+        * expensive for a large project, and its cost outweighs the
+        * savings we get by not inflating the object to a temporary
+        * file.  Practically, this code only helps when we are used
+        * by diff-cache --cached, which does read the cache before
+        * calling us.
+        */
+       if (!active_cache)
+               return 0;
+
+       len = strlen(name);
+       pos = cache_name_pos(name, len);
+       if (pos < 0)
+               return 0;
+       ce = active_cache[pos];
+       if ((lstat(name, &st) < 0) ||
+           !S_ISREG(st.st_mode) || /* careful! */
+           ce_match_stat(ce, &st, 0) ||
+           memcmp(sha1, ce->sha1, 20))
+               return 0;
+       /* we return 1 only when we can stat, it is a regular file,
+        * stat information matches, and sha1 recorded in the cache
+        * matches.  I.e. we know the file in the work tree really is
+        * the same as the <name, sha1> pair.
+        */
+       return 1;
+}
+
+static struct sha1_size_cache {
+       unsigned char sha1[20];
+       unsigned long size;
+} **sha1_size_cache;
+static int sha1_size_cache_nr, sha1_size_cache_alloc;
+
+static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
+                                                int find_only,
+                                                unsigned long size)
+{
+       int first, last;
+       struct sha1_size_cache *e;
+
+       first = 0;
+       last = sha1_size_cache_nr;
+       while (last > first) {
+               int cmp, next = (last + first) >> 1;
+               e = sha1_size_cache[next];
+               cmp = memcmp(e->sha1, sha1, 20);
+               if (!cmp)
+                       return e;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       /* not found */
+       if (find_only)
+               return NULL;
+       /* insert to make it at "first" */
+       if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
+               sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
+               sha1_size_cache = xrealloc(sha1_size_cache,
+                                          sha1_size_cache_alloc *
+                                          sizeof(*sha1_size_cache));
+       }
+       sha1_size_cache_nr++;
+       if (first < sha1_size_cache_nr)
+               memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
+                       (sha1_size_cache_nr - first - 1) *
+                       sizeof(*sha1_size_cache));
+       e = xmalloc(sizeof(struct sha1_size_cache));
+       sha1_size_cache[first] = e;
+       memcpy(e->sha1, sha1, 20);
+       e->size = size;
+       return e;
+}
+
+/*
+ * While doing rename detection and pickaxe operation, we may need to
+ * grab the data for the blob (or file) for our own in-core comparison.
+ * diff_filespec has data and size fields for this purpose.
+ */
+int diff_populate_filespec(struct diff_filespec *s, int size_only)
+{
+       int err = 0;
+       if (!DIFF_FILE_VALID(s))
+               die("internal error: asking to populate invalid file.");
+       if (S_ISDIR(s->mode))
+               return -1;
+
+       if (!use_size_cache)
+               size_only = 0;
+
+       if (s->data)
+               return err;
+       if (!s->sha1_valid ||
+           work_tree_matches(s->path, s->sha1)) {
+               struct stat st;
+               int fd;
+               if (lstat(s->path, &st) < 0) {
+                       if (errno == ENOENT) {
+                       err_empty:
+                               err = -1;
+                       empty:
+                               s->data = "";
+                               s->size = 0;
+                               return err;
+                       }
+               }
+               s->size = st.st_size;
+               if (!s->size)
+                       goto empty;
+               if (size_only)
+                       return 0;
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       s->data = xmalloc(s->size);
+                       s->should_free = 1;
+                       ret = readlink(s->path, s->data, s->size);
+                       if (ret < 0) {
+                               free(s->data);
+                               goto err_empty;
+                       }
+                       return 0;
+               }
+               fd = open(s->path, O_RDONLY);
+               if (fd < 0)
+                       goto err_empty;
+               s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+               close(fd);
+               if (s->data == MAP_FAILED)
+                       goto err_empty;
+               s->should_munmap = 1;
+       }
+       else {
+               char type[20];
+               struct sha1_size_cache *e;
+
+               if (size_only) {
+                       e = locate_size_cache(s->sha1, 1, 0);
+                       if (e) {
+                               s->size = e->size;
+                               return 0;
+                       }
+                       if (!sha1_object_info(s->sha1, type, &s->size))
+                               locate_size_cache(s->sha1, 0, s->size);
+               }
+               else {
+                       s->data = read_sha1_file(s->sha1, type, &s->size);
+                       s->should_free = 1;
+               }
+       }
+       return 0;
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+       if (s->should_free)
+               free(s->data);
+       else if (s->should_munmap)
+               munmap(s->data, s->size);
+       s->should_free = s->should_munmap = 0;
+       s->data = NULL;
+       free(s->cnt_data);
+       s->cnt_data = NULL;
+}
+
+static void prep_temp_blob(struct diff_tempfile *temp,
+                          void *blob,
+                          unsigned long size,
+                          const unsigned char *sha1,
+                          int mode)
+{
+       int fd;
+
+       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)
+               die("unable to write temp-file");
+       close(fd);
+       temp->name = temp->tmp_path;
+       strcpy(temp->hex, sha1_to_hex(sha1));
+       temp->hex[40] = 0;
+       sprintf(temp->mode, "%06o", mode);
+}
+
+static void prepare_temp_file(const char *name,
+                             struct diff_tempfile *temp,
+                             struct diff_filespec *one)
+{
+       if (!DIFF_FILE_VALID(one)) {
+       not_a_valid_file:
+               /* A '-' entry produces this for file-2, and
+                * a '+' entry produces this for file-1.
+                */
+               temp->name = "/dev/null";
+               strcpy(temp->hex, ".");
+               strcpy(temp->mode, ".");
+               return;
+       }
+
+       if (!one->sha1_valid ||
+           work_tree_matches(name, one->sha1)) {
+               struct stat st;
+               if (lstat(name, &st) < 0) {
+                       if (errno == ENOENT)
+                               goto not_a_valid_file;
+                       die("stat(%s): %s", name, strerror(errno));
+               }
+               if (S_ISLNK(st.st_mode)) {
+                       int ret;
+                       char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */
+                       if (sizeof(buf) <= st.st_size)
+                               die("symlink too long: %s", name);
+                       ret = readlink(name, buf, st.st_size);
+                       if (ret < 0)
+                               die("readlink(%s)", name);
+                       prep_temp_blob(temp, buf, st.st_size,
+                                      (one->sha1_valid ?
+                                       one->sha1 : null_sha1),
+                                      (one->sha1_valid ?
+                                       one->mode : S_IFLNK));
+               }
+               else {
+                       /* we can borrow from the file in the work tree */
+                       temp->name = name;
+                       if (!one->sha1_valid)
+                               strcpy(temp->hex, sha1_to_hex(null_sha1));
+                       else
+                               strcpy(temp->hex, sha1_to_hex(one->sha1));
+                       /* Even though we may sometimes borrow the
+                        * contents from the work tree, we always want
+                        * one->mode.  mode is trustworthy even when
+                        * !(one->sha1_valid), as long as
+                        * DIFF_FILE_VALID(one).
+                        */
+                       sprintf(temp->mode, "%06o", one->mode);
+               }
+               return;
+       }
+       else {
+               if (diff_populate_filespec(one, 0))
+                       die("cannot read data blob for %s", one->path);
+               prep_temp_blob(temp, one->data, one->size,
+                              one->sha1, one->mode);
+       }
+}
+
+static void remove_tempfile(void)
+{
+       int i;
+
+       for (i = 0; i < 2; i++)
+               if (diff_temp[i].name == diff_temp[i].tmp_path) {
+                       unlink(diff_temp[i].name);
+                       diff_temp[i].name = NULL;
+               }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+       remove_tempfile();
+       signal(SIGINT, SIG_DFL);
+       raise(signo);
+}
+
+static int spawn_prog(const char *pgm, const char **arg)
+{
+       pid_t pid;
+       int status;
+
+       fflush(NULL);
+       pid = fork();
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               execvp(pgm, (char *const*) arg);
+               exit(255);
+       }
+
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno == EINTR)
+                       continue;
+               return -1;
+       }
+
+       /* Earlier we did not check the exit status because
+        * diff exits non-zero if files are different, and
+        * we are not interested in knowing that.  It was a
+        * mistake which made it harder to quit a diff-*
+        * session that uses the git-apply-patch-script as
+        * the GIT_EXTERNAL_DIFF.  A custom GIT_EXTERNAL_DIFF
+        * should also exit non-zero only when it wants to
+        * abort the entire diff-* session.
+        */
+       if (WIFEXITED(status) && !WEXITSTATUS(status))
+               return 0;
+       return -1;
+}
+
+/* An external diff command takes:
+ *
+ * diff-cmd name infile1 infile1-sha1 infile1-mode \
+ *               infile2 infile2-sha1 infile2-mode [ rename-to ]
+ *
+ */
+static void run_external_diff(const char *pgm,
+                             const char *name,
+                             const char *other,
+                             struct diff_filespec *one,
+                             struct diff_filespec *two,
+                             const char *xfrm_msg,
+                             int complete_rewrite)
+{
+       const char *spawn_arg[10];
+       struct diff_tempfile *temp = diff_temp;
+       int retval;
+       static int atexit_asked = 0;
+       const char *othername;
+       const char **arg = &spawn_arg[0];
+
+       othername = (other? other : name);
+       if (one && two) {
+               prepare_temp_file(name, &temp[0], one);
+               prepare_temp_file(othername, &temp[1], two);
+               if (! atexit_asked &&
+                   (temp[0].name == temp[0].tmp_path ||
+                    temp[1].name == temp[1].tmp_path)) {
+                       atexit_asked = 1;
+                       atexit(remove_tempfile);
+               }
+               signal(SIGINT, remove_tempfile_on_signal);
+       }
+
+       if (one && two) {
+               *arg++ = pgm;
+               *arg++ = name;
+               *arg++ = temp[0].name;
+               *arg++ = temp[0].hex;
+               *arg++ = temp[0].mode;
+               *arg++ = temp[1].name;
+               *arg++ = temp[1].hex;
+               *arg++ = temp[1].mode;
+               if (other) {
+                       *arg++ = other;
+                       *arg++ = xfrm_msg;
+               }
+       } else {
+               *arg++ = pgm;
+               *arg++ = name;
+       }
+       *arg = NULL;
+       retval = spawn_prog(pgm, spawn_arg);
+       remove_tempfile();
+       if (retval) {
+               fprintf(stderr, "external diff died, stopping at %s.\n", name);
+               exit(1);
+       }
+}
+
+static void run_diff_cmd(const char *pgm,
+                        const char *name,
+                        const char *other,
+                        struct diff_filespec *one,
+                        struct diff_filespec *two,
+                        const char *xfrm_msg,
+                        struct diff_options *o,
+                        int complete_rewrite)
+{
+       if (pgm) {
+               run_external_diff(pgm, name, other, one, two, xfrm_msg,
+                                 complete_rewrite);
+               return;
+       }
+       if (one && two)
+               builtin_diff(name, other ? other : name,
+                            one, two, xfrm_msg, o, complete_rewrite);
+       else
+               printf("* Unmerged path %s\n", name);
+}
+
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+       if (DIFF_FILE_VALID(one)) {
+               if (!one->sha1_valid) {
+                       struct stat st;
+                       if (lstat(one->path, &st) < 0)
+                               die("stat %s", one->path);
+                       if (index_path(one->sha1, one->path, &st, 0))
+                               die("cannot hash %s\n", one->path);
+               }
+       }
+       else
+               memset(one->sha1, 0, 20);
+}
+
+static void run_diff(struct diff_filepair *p, struct diff_options *o)
+{
+       const char *pgm = external_diff();
+       char msg[PATH_MAX*2+300], *xfrm_msg;
+       struct diff_filespec *one;
+       struct diff_filespec *two;
+       const char *name;
+       const char *other;
+       char *name_munged, *other_munged;
+       int complete_rewrite = 0;
+       int len;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+       name_munged = quote_one(name);
+       other_munged = quote_one(other);
+       one = p->one; two = p->two;
+
+       diff_fill_sha1_info(one);
+       diff_fill_sha1_info(two);
+
+       len = 0;
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "copy from %s\n"
+                               "copy to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
+               break;
+       case DIFF_STATUS_RENAMED:
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "similarity index %d%%\n"
+                               "rename from %s\n"
+                               "rename to %s\n",
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
+                               name_munged, other_munged);
+               break;
+       case DIFF_STATUS_MODIFIED:
+               if (p->score) {
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       "dissimilarity index %d%%\n",
+                                       (int)(0.5 + p->score *
+                                             100.0/MAX_SCORE));
+                       complete_rewrite = 1;
+                       break;
+               }
+               /* fallthru */
+       default:
+               /* nothing */
+               ;
+       }
+
+       if (memcmp(one->sha1, two->sha1, 20)) {
+               int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
+
+               len += snprintf(msg + len, sizeof(msg) - len,
+                               "index %.*s..%.*s",
+                               abbrev, sha1_to_hex(one->sha1),
+                               abbrev, sha1_to_hex(two->sha1));
+               if (one->mode == two->mode)
+                       len += snprintf(msg + len, sizeof(msg) - len,
+                                       " %06o", one->mode);
+               len += snprintf(msg + len, sizeof(msg) - len, "\n");
+       }
+
+       if (len)
+               msg[--len] = 0;
+       xfrm_msg = len ? msg : NULL;
+
+       if (!pgm &&
+           DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
+           (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
+               /* a filepair that changes between file and symlink
+                * needs to be split into deletion and creation.
+                */
+               struct diff_filespec *null = alloc_filespec(two->path);
+               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
+               free(null);
+               null = alloc_filespec(one->path);
+               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
+               free(null);
+       }
+       else
+               run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
+                            complete_rewrite);
+
+       free(name_munged);
+       free(other_munged);
+}
+
+static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
+                        struct diffstat_t *diffstat)
+{
+       const char *name;
+       const char *other;
+       int complete_rewrite = 0;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+       diff_fill_sha1_info(p->one);
+       diff_fill_sha1_info(p->two);
+
+       if (p->status == DIFF_STATUS_MODIFIED && p->score)
+               complete_rewrite = 1;
+       builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
+}
+
+static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
+{
+       const char *name;
+       const char *other;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+       diff_fill_sha1_info(p->one);
+       diff_fill_sha1_info(p->two);
+
+       builtin_checkdiff(name, other, p->one, p->two);
+}
+
+void diff_setup(struct diff_options *options)
+{
+       memset(options, 0, sizeof(*options));
+       options->output_format = DIFF_FORMAT_RAW;
+       options->line_termination = '\n';
+       options->break_opt = -1;
+       options->rename_limit = -1;
+       options->context = 3;
+
+       options->change = diff_change;
+       options->add_remove = diff_addremove;
+}
+
+int diff_setup_done(struct diff_options *options)
+{
+       if ((options->find_copies_harder &&
+            options->detect_rename != DIFF_DETECT_COPY) ||
+           (0 <= options->rename_limit && !options->detect_rename))
+               return -1;
+
+       /*
+        * These cases always need recursive; we do not drop caller-supplied
+        * recursive bits for other formats here.
+        */
+       if ((options->output_format == DIFF_FORMAT_PATCH) ||
+           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
+           (options->output_format == DIFF_FORMAT_CHECKDIFF))
+               options->recursive = 1;
+
+       /*
+        * These combinations do not make sense.
+        */
+       if (options->output_format == DIFF_FORMAT_RAW)
+               options->with_raw = 0;
+       if (options->output_format == DIFF_FORMAT_DIFFSTAT)
+               options->with_stat  = 0;
+
+       if (options->detect_rename && options->rename_limit < 0)
+               options->rename_limit = diff_rename_limit_default;
+       if (options->setup & DIFF_SETUP_USE_CACHE) {
+               if (!active_cache)
+                       /* read-cache does not die even when it fails
+                        * so it is safe for us to do this here.  Also
+                        * it does not smudge active_cache or active_nr
+                        * when it fails, so we do not have to worry about
+                        * cleaning it up ourselves either.
+                        */
+                       read_cache();
+       }
+       if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
+               use_size_cache = 1;
+       if (options->abbrev <= 0 || 40 < options->abbrev)
+               options->abbrev = 40; /* full */
+
+       return 0;
+}
+
+int opt_arg(const char *arg, int arg_short, const char *arg_long, int *val)
+{
+       char c, *eq;
+       int len;
+
+       if (*arg != '-')
+               return 0;
+       c = *++arg;
+       if (!c)
+               return 0;
+       if (c == arg_short) {
+               c = *++arg;
+               if (!c)
+                       return 1;
+               if (val && isdigit(c)) {
+                       char *end;
+                       int n = strtoul(arg, &end, 10);
+                       if (*end)
+                               return 0;
+                       *val = n;
+                       return 1;
+               }
+               return 0;
+       }
+       if (c != '-')
+               return 0;
+       arg++;
+       eq = strchr(arg, '=');
+       if (eq)
+               len = eq - arg;
+       else
+               len = strlen(arg);
+       if (!len || strncmp(arg, arg_long, len))
+               return 0;
+       if (eq) {
+               int n;
+               char *end;
+               if (!isdigit(*++eq))
+                       return 0;
+               n = strtoul(eq, &end, 10);
+               if (*end)
+                       return 0;
+               *val = n;
+       }
+       return 1;
+}
+
+int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+{
+       const char *arg = av[0];
+       if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+               options->output_format = DIFF_FORMAT_PATCH;
+       else if (opt_arg(arg, 'U', "unified", &options->context))
+               options->output_format = DIFF_FORMAT_PATCH;
+       else if (!strcmp(arg, "--patch-with-raw")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->with_raw = 1;
+       }
+       else if (!strcmp(arg, "--stat"))
+               options->output_format = DIFF_FORMAT_DIFFSTAT;
+       else if (!strcmp(arg, "--check"))
+               options->output_format = DIFF_FORMAT_CHECKDIFF;
+       else if (!strcmp(arg, "--summary"))
+               options->summary = 1;
+       else if (!strcmp(arg, "--patch-with-stat")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->with_stat = 1;
+       }
+       else if (!strcmp(arg, "-z"))
+               options->line_termination = 0;
+       else if (!strncmp(arg, "-l", 2))
+               options->rename_limit = strtoul(arg+2, NULL, 10);
+       else if (!strcmp(arg, "--full-index"))
+               options->full_index = 1;
+       else if (!strcmp(arg, "--binary")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->full_index = options->binary = 1;
+       }
+       else if (!strcmp(arg, "--name-only"))
+               options->output_format = DIFF_FORMAT_NAME;
+       else if (!strcmp(arg, "--name-status"))
+               options->output_format = DIFF_FORMAT_NAME_STATUS;
+       else if (!strcmp(arg, "-R"))
+               options->reverse_diff = 1;
+       else if (!strncmp(arg, "-S", 2))
+               options->pickaxe = arg + 2;
+       else if (!strcmp(arg, "-s"))
+               options->output_format = DIFF_FORMAT_NO_OUTPUT;
+       else if (!strncmp(arg, "-O", 2))
+               options->orderfile = arg + 2;
+       else if (!strncmp(arg, "--diff-filter=", 14))
+               options->filter = arg + 14;
+       else if (!strcmp(arg, "--pickaxe-all"))
+               options->pickaxe_opts = DIFF_PICKAXE_ALL;
+       else if (!strcmp(arg, "--pickaxe-regex"))
+               options->pickaxe_opts = DIFF_PICKAXE_REGEX;
+       else if (!strncmp(arg, "-B", 2)) {
+               if ((options->break_opt =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+       }
+       else if (!strncmp(arg, "-M", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_RENAME;
+       }
+       else if (!strncmp(arg, "-C", 2)) {
+               if ((options->rename_score =
+                    diff_scoreopt_parse(arg)) == -1)
+                       return -1;
+               options->detect_rename = DIFF_DETECT_COPY;
+       }
+       else if (!strcmp(arg, "--find-copies-harder"))
+               options->find_copies_harder = 1;
+       else if (!strcmp(arg, "--abbrev"))
+               options->abbrev = DEFAULT_ABBREV;
+       else if (!strncmp(arg, "--abbrev=", 9)) {
+               options->abbrev = strtoul(arg + 9, NULL, 10);
+               if (options->abbrev < MINIMUM_ABBREV)
+                       options->abbrev = MINIMUM_ABBREV;
+               else if (40 < options->abbrev)
+                       options->abbrev = 40;
+       }
+       else
+               return 0;
+       return 1;
+}
+
+static int parse_num(const char **cp_p)
+{
+       unsigned long num, scale;
+       int ch, dot;
+       const char *cp = *cp_p;
+
+       num = 0;
+       scale = 1;
+       dot = 0;
+       for(;;) {
+               ch = *cp;
+               if ( !dot && ch == '.' ) {
+                       scale = 1;
+                       dot = 1;
+               } else if ( ch == '%' ) {
+                       scale = dot ? scale*100 : 100;
+                       cp++;   /* % is always at the end */
+                       break;
+               } else if ( ch >= '0' && ch <= '9' ) {
+                       if ( scale < 100000 ) {
+                               scale *= 10;
+                               num = (num*10) + (ch-'0');
+                       }
+               } else {
+                       break;
+               }
+               cp++;
+       }
+       *cp_p = cp;
+
+       /* user says num divided by scale and we say internally that
+        * is MAX_SCORE * num / scale.
+        */
+       return (num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale);
+}
+
+int diff_scoreopt_parse(const char *opt)
+{
+       int opt1, opt2, cmd;
+
+       if (*opt++ != '-')
+               return -1;
+       cmd = *opt++;
+       if (cmd != 'M' && cmd != 'C' && cmd != 'B')
+               return -1; /* that is not a -M, -C nor -B option */
+
+       opt1 = parse_num(&opt);
+       if (cmd != 'B')
+               opt2 = 0;
+       else {
+               if (*opt == 0)
+                       opt2 = 0;
+               else if (*opt != '/')
+                       return -1; /* we expect -B80/99 or -B80 */
+               else {
+                       opt++;
+                       opt2 = parse_num(&opt);
+               }
+       }
+       if (*opt != 0)
+               return -1;
+       return opt1 | (opt2 << 16);
+}
+
+struct diff_queue_struct diff_queued_diff;
+
+void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
+{
+       if (queue->alloc <= queue->nr) {
+               queue->alloc = alloc_nr(queue->alloc);
+               queue->queue = xrealloc(queue->queue,
+                                       sizeof(dp) * queue->alloc);
+       }
+       queue->queue[queue->nr++] = dp;
+}
+
+struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
+                                struct diff_filespec *one,
+                                struct diff_filespec *two)
+{
+       struct diff_filepair *dp = xmalloc(sizeof(*dp));
+       dp->one = one;
+       dp->two = two;
+       dp->score = 0;
+       dp->status = 0;
+       dp->source_stays = 0;
+       dp->broken_pair = 0;
+       if (queue)
+               diff_q(queue, dp);
+       return dp;
+}
+
+void diff_free_filepair(struct diff_filepair *p)
+{
+       diff_free_filespec_data(p->one);
+       diff_free_filespec_data(p->two);
+       free(p->one);
+       free(p->two);
+       free(p);
+}
+
+/* This is different from find_unique_abbrev() in that
+ * it stuffs the result with dots for alignment.
+ */
+const char *diff_unique_abbrev(const unsigned char *sha1, int len)
+{
+       int abblen;
+       const char *abbrev;
+       if (len == 40)
+               return sha1_to_hex(sha1);
+
+       abbrev = find_unique_abbrev(sha1, len);
+       if (!abbrev)
+               return sha1_to_hex(sha1);
+       abblen = strlen(abbrev);
+       if (abblen < 37) {
+               static char hex[41];
+               if (len < abblen && abblen <= len + 2)
+                       sprintf(hex, "%s%.*s", abbrev, len+3-abblen, "..");
+               else
+                       sprintf(hex, "%s...", abbrev);
+               return hex;
+       }
+       return sha1_to_hex(sha1);
+}
+
+static void diff_flush_raw(struct diff_filepair *p,
+                          int line_termination,
+                          int inter_name_termination,
+                          struct diff_options *options,
+                          int output_format)
+{
+       int two_paths;
+       char status[10];
+       int abbrev = options->abbrev;
+       const char *path_one, *path_two;
+
+       path_one = p->one->path;
+       path_two = p->two->path;
+       if (line_termination) {
+               path_one = quote_one(path_one);
+               path_two = quote_one(path_two);
+       }
+
+       if (p->score)
+               sprintf(status, "%c%03d", p->status,
+                       (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       else {
+               status[0] = p->status;
+               status[1] = 0;
+       }
+       switch (p->status) {
+       case DIFF_STATUS_COPIED:
+       case DIFF_STATUS_RENAMED:
+               two_paths = 1;
+               break;
+       case DIFF_STATUS_ADDED:
+       case DIFF_STATUS_DELETED:
+               two_paths = 0;
+               break;
+       default:
+               two_paths = 0;
+               break;
+       }
+       if (output_format != DIFF_FORMAT_NAME_STATUS) {
+               printf(":%06o %06o %s ",
+                      p->one->mode, p->two->mode,
+                      diff_unique_abbrev(p->one->sha1, abbrev));
+               printf("%s ",
+                      diff_unique_abbrev(p->two->sha1, abbrev));
+       }
+       printf("%s%c%s", status, inter_name_termination, path_one);
+       if (two_paths)
+               printf("%c%s", inter_name_termination, path_two);
+       putchar(line_termination);
+       if (path_one != p->one->path)
+               free((void*)path_one);
+       if (path_two != p->two->path)
+               free((void*)path_two);
+}
+
+static void diff_flush_name(struct diff_filepair *p,
+                           int inter_name_termination,
+                           int line_termination)
+{
+       char *path = p->two->path;
+
+       if (line_termination)
+               path = quote_one(p->two->path);
+       else
+               path = p->two->path;
+       printf("%s%c", path, line_termination);
+       if (p->two->path != path)
+               free(path);
+}
+
+int diff_unmodified_pair(struct diff_filepair *p)
+{
+       /* This function is written stricter than necessary to support
+        * the currently implemented transformers, but the idea is to
+        * let transformers to produce diff_filepairs any way they want,
+        * and filter and clean them up here before producing the output.
+        */
+       struct diff_filespec *one, *two;
+
+       if (DIFF_PAIR_UNMERGED(p))
+               return 0; /* unmerged is interesting */
+
+       one = p->one;
+       two = p->two;
+
+       /* deletion, addition, mode or type change
+        * and rename are all interesting.
+        */
+       if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
+           DIFF_PAIR_MODE_CHANGED(p) ||
+           strcmp(one->path, two->path))
+               return 0;
+
+       /* both are valid and point at the same path.  that is, we are
+        * dealing with a change.
+        */
+       if (one->sha1_valid && two->sha1_valid &&
+           !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+               return 1; /* no change */
+       if (!one->sha1_valid && !two->sha1_valid)
+               return 1; /* both look at the same file on the filesystem. */
+       return 0;
+}
+
+static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_diff(p, o);
+}
+
+static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
+                           struct diffstat_t *diffstat)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_diffstat(p, o, diffstat);
+}
+
+static void diff_flush_checkdiff(struct diff_filepair *p,
+               struct diff_options *o)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_checkdiff(p, o);
+}
+
+int diff_queue_is_empty(void)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       for (i = 0; i < q->nr; i++)
+               if (!diff_unmodified_pair(q->queue[i]))
+                       return 0;
+       return 1;
+}
+
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
+{
+       fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
+               x, one ? one : "",
+               s->path,
+               DIFF_FILE_VALID(s) ? "valid" : "invalid",
+               s->mode,
+               s->sha1_valid ? sha1_to_hex(s->sha1) : "");
+       fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+               x, one ? one : "",
+               s->size, s->xfrm_flags);
+}
+
+void diff_debug_filepair(const struct diff_filepair *p, int i)
+{
+       diff_debug_filespec(p->one, i, "one");
+       diff_debug_filespec(p->two, i, "two");
+       fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+               p->score, p->status ? p->status : '?',
+               p->source_stays, p->broken_pair);
+}
+
+void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
+{
+       int i;
+       if (msg)
+               fprintf(stderr, "%s\n", msg);
+       fprintf(stderr, "q->nr = %d\n", q->nr);
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_debug_filepair(p, i);
+       }
+}
+#endif
+
+static void diff_resolve_rename_copy(void)
+{
+       int i, j;
+       struct diff_filepair *p, *pp;
+       struct diff_queue_struct *q = &diff_queued_diff;
+
+       diff_debug_queue("resolve-rename-copy", q);
+
+       for (i = 0; i < q->nr; i++) {
+               p = q->queue[i];
+               p->status = 0; /* undecided */
+               if (DIFF_PAIR_UNMERGED(p))
+                       p->status = DIFF_STATUS_UNMERGED;
+               else if (!DIFF_FILE_VALID(p->one))
+                       p->status = DIFF_STATUS_ADDED;
+               else if (!DIFF_FILE_VALID(p->two))
+                       p->status = DIFF_STATUS_DELETED;
+               else if (DIFF_PAIR_TYPE_CHANGED(p))
+                       p->status = DIFF_STATUS_TYPE_CHANGED;
+
+               /* from this point on, we are dealing with a pair
+                * whose both sides are valid and of the same type, i.e.
+                * either in-place edit or rename/copy edit.
+                */
+               else if (DIFF_PAIR_RENAME(p)) {
+                       if (p->source_stays) {
+                               p->status = DIFF_STATUS_COPIED;
+                               continue;
+                       }
+                       /* See if there is some other filepair that
+                        * copies from the same source as us.  If so
+                        * we are a copy.  Otherwise we are either a
+                        * copy if the path stays, or a rename if it
+                        * does not, but we already handled "stays" case.
+                        */
+                       for (j = i + 1; j < q->nr; j++) {
+                               pp = q->queue[j];
+                               if (strcmp(pp->one->path, p->one->path))
+                                       continue; /* not us */
+                               if (!DIFF_PAIR_RENAME(pp))
+                                       continue; /* not a rename/copy */
+                               /* pp is a rename/copy from the same source */
+                               p->status = DIFF_STATUS_COPIED;
+                               break;
+                       }
+                       if (!p->status)
+                               p->status = DIFF_STATUS_RENAMED;
+               }
+               else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+                        p->one->mode != p->two->mode)
+                       p->status = DIFF_STATUS_MODIFIED;
+               else {
+                       /* This is a "no-change" entry and should not
+                        * happen anymore, but prepare for broken callers.
+                        */
+                       error("feeding unmodified %s to diffcore",
+                             p->one->path);
+                       p->status = DIFF_STATUS_UNKNOWN;
+               }
+       }
+       diff_debug_queue("resolve-rename-copy done", q);
+}
+
+static void flush_one_pair(struct diff_filepair *p,
+                          int diff_output_format,
+                          struct diff_options *options,
+                          struct diffstat_t *diffstat)
+{
+       int inter_name_termination = '\t';
+       int line_termination = options->line_termination;
+       if (!line_termination)
+               inter_name_termination = 0;
+
+       switch (p->status) {
+       case DIFF_STATUS_UNKNOWN:
+               break;
+       case 0:
+               die("internal error in diff-resolve-rename-copy");
+               break;
+       default:
+               switch (diff_output_format) {
+               case DIFF_FORMAT_DIFFSTAT:
+                       diff_flush_stat(p, options, diffstat);
+                       break;
+               case DIFF_FORMAT_CHECKDIFF:
+                       diff_flush_checkdiff(p, options);
+                       break;
+               case DIFF_FORMAT_PATCH:
+                       diff_flush_patch(p, options);
+                       break;
+               case DIFF_FORMAT_RAW:
+               case DIFF_FORMAT_NAME_STATUS:
+                       diff_flush_raw(p, line_termination,
+                                      inter_name_termination,
+                                      options, diff_output_format);
+                       break;
+               case DIFF_FORMAT_NAME:
+                       diff_flush_name(p,
+                                       inter_name_termination,
+                                       line_termination);
+                       break;
+               case DIFF_FORMAT_NO_OUTPUT:
+                       break;
+               }
+       }
+}
+
+static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
+{
+       if (fs->mode)
+               printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+       else
+               printf(" %s %s\n", newdelete, fs->path);
+}
+
+
+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)
+                       printf(" mode change %06o => %06o %s\n",
+                              p->one->mode, p->two->mode, p->two->path);
+               else
+                       printf(" mode change %06o => %06o\n",
+                              p->one->mode, p->two->mode);
+       }
+}
+
+static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
+{
+       const char *old, *new;
+
+       /* 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));
+       show_mode_change(p, 0);
+}
+
+static void diff_summary(struct diff_filepair *p)
+{
+       switch(p->status) {
+       case DIFF_STATUS_DELETED:
+               show_file_mode_name("delete", p->one);
+               break;
+       case DIFF_STATUS_ADDED:
+               show_file_mode_name("create", p->two);
+               break;
+       case DIFF_STATUS_COPIED:
+               show_rename_copy("copy", p);
+               break;
+       case DIFF_STATUS_RENAMED:
+               show_rename_copy("rename", p);
+               break;
+       default:
+               if (p->score) {
+                       printf(" rewrite %s (%d%%)\n", p->two->path,
+                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                       show_mode_change(p, 0);
+               } else  show_mode_change(p, 1);
+               break;
+       }
+}
+
+void diff_flush(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       int i;
+       int diff_output_format = options->output_format;
+       struct diffstat_t *diffstat = NULL;
+
+       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
+               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
+               diffstat->xm.consume = diffstat_consume;
+       }
+
+       if (options->with_raw) {
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+               }
+               putchar(options->line_termination);
+       }
+       if (options->with_stat) {
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
+                                      diffstat);
+               }
+               show_stats(diffstat);
+               free(diffstat);
+               diffstat = NULL;
+               if (options->summary)
+                       for (i = 0; i < q->nr; i++)
+                               diff_summary(q->queue[i]);
+               if (options->stat_sep)
+                       fputs(options->stat_sep, stdout);
+               else
+                       putchar(options->line_termination);
+       }
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               flush_one_pair(p, diff_output_format, options, diffstat);
+       }
+
+       if (diffstat) {
+               show_stats(diffstat);
+               free(diffstat);
+       }
+
+       for (i = 0; i < q->nr; i++) {
+               if (diffstat && options->summary)
+                       diff_summary(q->queue[i]);
+               diff_free_filepair(q->queue[i]);
+       }
+
+       free(q->queue);
+       q->queue = NULL;
+       q->nr = q->alloc = 0;
+}
+
+static void diffcore_apply_filter(const char *filter)
+{
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       if (!filter)
+               return;
+
+       if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+               int found;
+               for (i = found = 0; !found && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               found++;
+               }
+               if (found)
+                       return;
+
+               /* otherwise we will clear the whole queue
+                * by copying the empty outq at the end of this
+                * function, but first clear the current entries
+                * in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       }
+       else {
+               /* Only the matching ones */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+
+                       if (((p->status == DIFF_STATUS_MODIFIED) &&
+                            ((p->score &&
+                              strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+                             (!p->score &&
+                              strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+                           ((p->status != DIFF_STATUS_MODIFIED) &&
+                            strchr(filter, p->status)))
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+       }
+       free(q->queue);
+       *q = outq;
+}
+
+void diffcore_std(struct diff_options *options)
+{
+       if (options->break_opt != -1)
+               diffcore_break(options->break_opt);
+       if (options->detect_rename)
+               diffcore_rename(options);
+       if (options->break_opt != -1)
+               diffcore_merge_broken();
+       if (options->pickaxe)
+               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+       if (options->orderfile)
+               diffcore_order(options->orderfile);
+       diff_resolve_rename_copy();
+       diffcore_apply_filter(options->filter);
+}
+
+
+void diffcore_std_no_resolve(struct diff_options *options)
+{
+       if (options->pickaxe)
+               diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+       if (options->orderfile)
+               diffcore_order(options->orderfile);
+       diffcore_apply_filter(options->filter);
+}
+
+void diff_addremove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       /* This may look odd, but it is a preparation for
+        * feeding "there are unchanged files which should
+        * not produce diffs, but when you are doing copy
+        * detection you would need them, so here they are"
+        * entries to the diff-core.  They will be prefixed
+        * with something like '=' or '*' (I haven't decided
+        * which but should not make any difference).
+        * Feeding the same new and old to diff_change() 
+        * also has the same effect.
+        * Before the final output happens, they are pruned after
+        * merged into rename/copy pairs as appropriate.
+        */
+       if (options->reverse_diff)
+               addremove = (addremove == '+' ? '-' :
+                            addremove == '-' ? '+' : addremove);
+
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+
+       if (addremove != '+')
+               fill_filespec(one, sha1, mode);
+       if (addremove != '-')
+               fill_filespec(two, sha1, mode);
+
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path) 
+{
+       char concatpath[PATH_MAX];
+       struct diff_filespec *one, *two;
+
+       if (options->reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+       }
+       if (!path) path = "";
+       sprintf(concatpath, "%s%s", base, path);
+       one = alloc_filespec(concatpath);
+       two = alloc_filespec(concatpath);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_unmerge(struct diff_options *options,
+                 const char *path)
+{
+       struct diff_filespec *one, *two;
+       one = alloc_filespec(path);
+       two = alloc_filespec(path);
+       diff_queue(&diff_queued_diff, one, two);
+}
diff --git a/diff.h b/diff.h
new file mode 100644 (file)
index 0000000..4fc597c
--- /dev/null
+++ b/diff.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef DIFF_H
+#define DIFF_H
+
+#include "tree-walk.h"
+
+struct rev_info;
+struct diff_options;
+
+typedef void (*change_fn_t)(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path);
+
+typedef void (*add_remove_fn_t)(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path);
+
+struct diff_options {
+       const char *filter;
+       const char *orderfile;
+       const char *pickaxe;
+       unsigned recursive:1,
+                with_raw:1,
+                with_stat:1,
+                tree_in_recursive:1,
+                binary:1,
+                full_index:1,
+                silent_on_remove:1,
+                find_copies_harder:1,
+                summary:1;
+       int context;
+       int break_opt;
+       int detect_rename;
+       int line_termination;
+       int output_format;
+       int pickaxe_opts;
+       int rename_score;
+       int reverse_diff;
+       int rename_limit;
+       int setup;
+       int abbrev;
+       const char *stat_sep;
+
+       int nr_paths;
+       const char **paths;
+       int *pathlens;
+       change_fn_t change;
+       add_remove_fn_t add_remove;
+};
+
+extern const char mime_boundary_leader[];
+
+extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
+extern void diff_tree_release_paths(struct diff_options *);
+extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+                    const char *base, struct diff_options *opt);
+extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+                         const char *base, struct diff_options *opt);
+
+struct combine_diff_path {
+       struct combine_diff_path *next;
+       int len;
+       char *path;
+       unsigned int mode;
+       unsigned char sha1[20];
+       struct combine_diff_parent {
+               char status;
+               unsigned int mode;
+               unsigned char sha1[20];
+       } parent[FLEX_ARRAY];
+};
+#define combine_diff_path_size(n, l) \
+       (sizeof(struct combine_diff_path) + \
+        sizeof(struct combine_diff_parent) * (n) + (l) + 1)
+
+extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
+                             int dense, struct rev_info *);
+
+extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
+
+extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
+
+extern void diff_addremove(struct diff_options *,
+                          int addremove,
+                          unsigned mode,
+                          const unsigned char *sha1,
+                          const char *base,
+                          const char *path);
+
+extern void diff_change(struct diff_options *,
+                       unsigned mode1, unsigned mode2,
+                       const unsigned char *sha1,
+                       const unsigned char *sha2,
+                       const char *base, const char *path);
+
+extern void diff_unmerge(struct diff_options *,
+                        const char *path);
+
+extern int diff_scoreopt_parse(const char *opt);
+
+#define DIFF_SETUP_REVERSE             1
+#define DIFF_SETUP_USE_CACHE           2
+#define DIFF_SETUP_USE_SIZE_CACHE      4
+
+extern int git_diff_config(const char *var, const char *value);
+extern void diff_setup(struct diff_options *);
+extern int diff_opt_parse(struct diff_options *, const char **, int);
+extern int diff_setup_done(struct diff_options *);
+
+#define DIFF_DETECT_RENAME     1
+#define DIFF_DETECT_COPY       2
+
+#define DIFF_PICKAXE_ALL       1
+#define DIFF_PICKAXE_REGEX     2
+
+extern void diffcore_std(struct diff_options *);
+
+extern void diffcore_std_no_resolve(struct diff_options *);
+
+#define COMMON_DIFF_OPTIONS_HELP \
+"\ncommon diff options:\n" \
+"  -z            output diff-raw with lines terminated with NUL.\n" \
+"  -p            output patch format.\n" \
+"  -u            synonym for -p.\n" \
+"  --patch-with-raw\n" \
+"                output both a patch and the diff-raw format.\n" \
+"  --stat        show diffstat instead of patch.\n" \
+"  --patch-with-stat\n" \
+"                output a patch and prepend its diffstat.\n" \
+"  --name-only   show only names of changed files.\n" \
+"  --name-status show names and status of changed files.\n" \
+"  --full-index  show full object name on index lines.\n" \
+"  --abbrev=<n>  abbreviate object names in diff-tree header and diff-raw.\n" \
+"  -R            swap input file pairs.\n" \
+"  -B            detect complete rewrites.\n" \
+"  -M            detect renames.\n" \
+"  -C            detect copies.\n" \
+"  --find-copies-harder\n" \
+"                try unchanged files as candidate for copy detection.\n" \
+"  -l<n>         limit rename attempts up to <n> paths.\n" \
+"  -O<file>      reorder diffs according to the <file>.\n" \
+"  -S<string>    find filepair whose only one side contains the string.\n" \
+"  --pickaxe-all\n" \
+"                show all files diff when -S is used and hit is found.\n"
+
+extern int diff_queue_is_empty(void);
+
+#define DIFF_FORMAT_RAW                1
+#define DIFF_FORMAT_PATCH      2
+#define DIFF_FORMAT_NO_OUTPUT  3
+#define DIFF_FORMAT_NAME       4
+#define DIFF_FORMAT_NAME_STATUS        5
+#define DIFF_FORMAT_DIFFSTAT   6
+#define DIFF_FORMAT_CHECKDIFF  7
+
+extern void diff_flush(struct diff_options*);
+
+/* diff-raw status letters */
+#define DIFF_STATUS_ADDED              'A'
+#define DIFF_STATUS_COPIED             'C'
+#define DIFF_STATUS_DELETED            'D'
+#define DIFF_STATUS_MODIFIED           'M'
+#define DIFF_STATUS_RENAMED            'R'
+#define DIFF_STATUS_TYPE_CHANGED       'T'
+#define DIFF_STATUS_UNKNOWN            'X'
+#define DIFF_STATUS_UNMERGED           'U'
+
+/* these are not diff-raw status letters proper, but used by
+ * diffcore-filter insn to specify additional restrictions.
+ */
+#define DIFF_STATUS_FILTER_AON         '*'
+#define DIFF_STATUS_FILTER_BROKEN      'B'
+
+extern const char *diff_unique_abbrev(const unsigned char *, int);
+
+extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
+
+extern int run_diff_index(struct rev_info *revs, int cached);
+
+#endif /* DIFF_H */
diff --git a/diffcore-break.c b/diffcore-break.c
new file mode 100644 (file)
index 0000000..ed0e14c
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+static int should_break(struct diff_filespec *src,
+                       struct diff_filespec *dst,
+                       int break_score,
+                       int *merge_score_p)
+{
+       /* dst is recorded as a modification of src.  Are they so
+        * different that we are better off recording this as a pair
+        * of delete and create?
+        *
+        * There are two criteria used in this algorithm.  For the
+        * purposes of helping later rename/copy, we take both delete
+        * and insert into account and estimate the amount of "edit".
+        * If the edit is very large, we break this pair so that
+        * rename/copy can pick the pieces up to match with other
+        * files.
+        *
+        * On the other hand, we would want to ignore inserts for the
+        * pure "complete rewrite" detection.  As long as most of the
+        * existing contents were removed from the file, it is a
+        * complete rewrite, and if sizable chunk from the original
+        * still remains in the result, it is not a rewrite.  It does
+        * not matter how much or how little new material is added to
+        * the file.
+        *
+        * The score we leave for such a broken filepair uses the
+        * latter definition so that later clean-up stage can find the
+        * pieces that should not have been broken according to the
+        * latter definition after rename/copy runs, and merge the
+        * broken pair that have a score lower than given criteria
+        * back together.  The break operation itself happens
+        * according to the former definition.
+        *
+        * The minimum_edit parameter tells us when to break (the
+        * amount of "edit" required for us to consider breaking the
+        * pair).  We leave the amount of deletion in *merge_score_p
+        * when we return.
+        *
+        * The value we return is 1 if we want the pair to be broken,
+        * or 0 if we do not.
+        */
+       unsigned long delta_size, base_size, src_copied, literal_added,
+               src_removed;
+
+       *merge_score_p = 0; /* assume no deletion --- "do not break"
+                            * is the default.
+                            */
+
+       if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+               return 0; /* leave symlink rename alone */
+
+       if (src->sha1_valid && dst->sha1_valid &&
+           !memcmp(src->sha1, dst->sha1, 20))
+               return 0; /* they are the same */
+
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0; /* error but caught downstream */
+
+       base_size = ((src->size < dst->size) ? src->size : dst->size);
+       if (base_size < MINIMUM_BREAK_SIZE)
+               return 0; /* we do not break too small filepair */
+
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  NULL, NULL,
+                                  0,
+                                  &src_copied, &literal_added))
+               return 0;
+
+       /* sanity */
+       if (src->size < src_copied)
+               src_copied = src->size;
+       if (dst->size < literal_added + src_copied) {
+               if (src_copied < dst->size)
+                       literal_added = dst->size - src_copied;
+               else
+                       literal_added = 0;
+       }
+       src_removed = src->size - src_copied;
+
+       /* Compute merge-score, which is "how much is removed
+        * from the source material".  The clean-up stage will
+        * merge the surviving pair together if the score is
+        * less than the minimum, after rename/copy runs.
+        */
+       *merge_score_p = src_removed * MAX_SCORE / src->size;
+
+       /* Extent of damage, which counts both inserts and
+        * deletes.
+        */
+       delta_size = src_removed + literal_added;
+       if (delta_size * MAX_SCORE / base_size < break_score)
+               return 0;
+
+       /* If you removed a lot without adding new material, that is
+        * not really a rewrite.
+        */
+       if ((src->size * break_score < src_removed * MAX_SCORE) &&
+           (literal_added * 20 < src_removed) &&
+           (literal_added * 20 < src_copied))
+               return 0;
+
+       return 1;
+}
+
+void diffcore_break(int break_score)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+
+       /* When the filepair has this much edit (insert and delete),
+        * it is first considered to be a rewrite and broken into a
+        * create and delete filepair.  This is to help breaking a
+        * file that had too much new stuff added, possibly from
+        * moving contents from another file, so that rename/copy can
+        * match it with the other file.
+        *
+        * int break_score; we reuse incoming parameter for this.
+        */
+
+       /* After a pair is broken according to break_score and
+        * subjected to rename/copy, both of them may survive intact,
+        * due to lack of suitable rename/copy peer.  Or, the caller
+        * may be calling us without using rename/copy.  When that
+        * happens, we merge the broken pieces back into one
+        * modification together if the pair did not have more than
+        * this much delete.  For this computation, we do not take
+        * insert into account at all.  If you start from a 100-line
+        * file and delete 97 lines of it, it does not matter if you
+        * add 27 lines to it to make a new 30-line file or if you add
+        * 997 lines to it to make a 1000-line file.  Either way what
+        * you did was a rewrite of 97%.  On the other hand, if you
+        * delete 3 lines, keeping 97 lines intact, it does not matter
+        * if you add 3 lines to it to make a new 100-line file or if
+        * you add 903 lines to it to make a new 1000-line file.
+        * Either way you did a lot of additions and not a rewrite.
+        * This merge happens to catch the latter case.  A merge_score
+        * of 80% would be a good default value (a broken pair that
+        * has score lower than merge_score will be merged back
+        * together).
+        */
+       int merge_score;
+       int i;
+
+       /* See comment on DEFAULT_BREAK_SCORE and
+        * DEFAULT_MERGE_SCORE in diffcore.h
+        */
+       merge_score = (break_score >> 16) & 0xFFFF;
+       break_score = (break_score & 0xFFFF);
+
+       if (!break_score)
+               break_score = DEFAULT_BREAK_SCORE;
+       if (!merge_score)
+               merge_score = DEFAULT_MERGE_SCORE;
+
+       outq.nr = outq.alloc = 0;
+       outq.queue = NULL;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               int score;
+
+               /* We deal only with in-place edit of non directory.
+                * We do not break anything else.
+                */
+               if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
+                   !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) &&
+                   !strcmp(p->one->path, p->two->path)) {
+                       if (should_break(p->one, p->two,
+                                        break_score, &score)) {
+                               /* Split this into delete and create */
+                               struct diff_filespec *null_one, *null_two;
+                               struct diff_filepair *dp;
+
+                               /* Set score to 0 for the pair that
+                                * needs to be merged back together
+                                * should they survive rename/copy.
+                                * Also we do not want to break very
+                                * small files.
+                                */
+                               if (score < merge_score)
+                                       score = 0;
+
+                               /* deletion of one */
+                               null_one = alloc_filespec(p->one->path);
+                               dp = diff_queue(&outq, p->one, null_one);
+                               dp->score = score;
+                               dp->broken_pair = 1;
+
+                               /* creation of two */
+                               null_two = alloc_filespec(p->two->path);
+                               dp = diff_queue(&outq, null_two, p->two);
+                               dp->score = score;
+                               dp->broken_pair = 1;
+
+                               free(p); /* not diff_free_filepair(), we are
+                                         * reusing one and two here.
+                                         */
+                               continue;
+                       }
+               }
+               diff_q(&outq, p);
+       }
+       free(q->queue);
+       *q = outq;
+
+       return;
+}
+
+static void merge_broken(struct diff_filepair *p,
+                        struct diff_filepair *pp,
+                        struct diff_queue_struct *outq)
+{
+       /* p and pp are broken pairs we want to merge */
+       struct diff_filepair *c = p, *d = pp, *dp;
+       if (DIFF_FILE_VALID(p->one)) {
+               /* this must be a delete half */
+               d = p; c = pp;
+       }
+       /* Sanity check */
+       if (!DIFF_FILE_VALID(d->one))
+               die("internal error in merge #1");
+       if (DIFF_FILE_VALID(d->two))
+               die("internal error in merge #2");
+       if (DIFF_FILE_VALID(c->one))
+               die("internal error in merge #3");
+       if (!DIFF_FILE_VALID(c->two))
+               die("internal error in merge #4");
+
+       dp = diff_queue(outq, d->one, c->two);
+       dp->score = p->score;
+       diff_free_filespec_data(d->two);
+       diff_free_filespec_data(c->one);
+       free(d);
+       free(c);
+}
+
+void diffcore_merge_broken(void)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       int i, j;
+
+       outq.nr = outq.alloc = 0;
+       outq.queue = NULL;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!p)
+                       /* we already merged this with its peer */
+                       continue;
+               else if (p->broken_pair &&
+                        !strcmp(p->one->path, p->two->path)) {
+                       /* If the peer also survived rename/copy, then
+                        * we merge them back together.
+                        */
+                       for (j = i + 1; j < q->nr; j++) {
+                               struct diff_filepair *pp = q->queue[j];
+                               if (pp->broken_pair &&
+                                   !strcmp(pp->one->path, pp->two->path) &&
+                                   !strcmp(p->one->path, pp->two->path)) {
+                                       /* Peer survived.  Merge them */
+                                       merge_broken(p, pp, &outq);
+                                       q->queue[j] = NULL;
+                                       break;
+                               }
+                       }
+                       if (q->nr <= j)
+                               /* The peer did not survive, so we keep
+                                * it in the output.
+                                */
+                               diff_q(&outq, p);
+               }
+               else
+                       diff_q(&outq, p);
+       }
+       free(q->queue);
+       *q = outq;
+
+       return;
+}
diff --git a/diffcore-delta.c b/diffcore-delta.c
new file mode 100644 (file)
index 0000000..7338a40
--- /dev/null
@@ -0,0 +1,213 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+/*
+ * Idea here is very simple.
+ *
+ * We have total of (sz-N+1) N-byte overlapping sequences in buf whose
+ * size is sz.  If the same N-byte sequence appears in both source and
+ * destination, we say the byte that starts that sequence is shared
+ * between them (i.e. copied from source to destination).
+ *
+ * For each possible N-byte sequence, if the source buffer has more
+ * instances of it than the destination buffer, that means the
+ * difference are the number of bytes not copied from source to
+ * destination.  If the counts are the same, everything was copied
+ * from source to destination.  If the destination has more,
+ * everything was copied, and destination added more.
+ *
+ * We are doing an approximation so we do not really have to waste
+ * memory by actually storing the sequence.  We just hash them into
+ * somewhere around 2^16 hashbuckets and count the occurrences.
+ *
+ * The length of the sequence is arbitrarily set to 8 for now.
+ */
+
+/* Wild guess at the initial hash size */
+#define INITIAL_HASH_SIZE 9
+
+/* We leave more room in smaller hash but do not let it
+ * grow to have unused hole too much.
+ */
+#define INITIAL_FREE(sz_log2) ((1<<(sz_log2))*(sz_log2-3)/(sz_log2))
+
+/* A prime rather carefully chosen between 2^16..2^17, so that
+ * HASHBASE < INITIAL_FREE(17).  We want to keep the maximum hashtable
+ * size under the current 2<<17 maximum, which can hold this many
+ * different values before overflowing to hashtable of size 2<<18.
+ */
+#define HASHBASE 107927
+
+struct spanhash {
+       unsigned int hashval;
+       unsigned int cnt;
+};
+struct spanhash_top {
+       int alloc_log2;
+       int free;
+       struct spanhash data[FLEX_ARRAY];
+};
+
+static struct spanhash *spanhash_find(struct spanhash_top *top,
+                                     unsigned int hashval)
+{
+       int sz = 1 << top->alloc_log2;
+       int bucket = hashval & (sz - 1);
+       while (1) {
+               struct spanhash *h = &(top->data[bucket++]);
+               if (!h->cnt)
+                       return NULL;
+               if (h->hashval == hashval)
+                       return h;
+               if (sz <= bucket)
+                       bucket = 0;
+       }
+}
+
+static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig)
+{
+       struct spanhash_top *new;
+       int i;
+       int osz = 1 << orig->alloc_log2;
+       int sz = osz << 1;
+
+       new = xmalloc(sizeof(*orig) + sizeof(struct spanhash) * sz);
+       new->alloc_log2 = orig->alloc_log2 + 1;
+       new->free = INITIAL_FREE(new->alloc_log2);
+       memset(new->data, 0, sizeof(struct spanhash) * sz);
+       for (i = 0; i < osz; i++) {
+               struct spanhash *o = &(orig->data[i]);
+               int bucket;
+               if (!o->cnt)
+                       continue;
+               bucket = o->hashval & (sz - 1);
+               while (1) {
+                       struct spanhash *h = &(new->data[bucket++]);
+                       if (!h->cnt) {
+                               h->hashval = o->hashval;
+                               h->cnt = o->cnt;
+                               new->free--;
+                               break;
+                       }
+                       if (sz <= bucket)
+                               bucket = 0;
+               }
+       }
+       free(orig);
+       return new;
+}
+
+static struct spanhash_top *add_spanhash(struct spanhash_top *top,
+                                        unsigned int hashval, int cnt)
+{
+       int bucket, lim;
+       struct spanhash *h;
+
+       lim = (1 << top->alloc_log2);
+       bucket = hashval & (lim - 1);
+       while (1) {
+               h = &(top->data[bucket++]);
+               if (!h->cnt) {
+                       h->hashval = hashval;
+                       h->cnt = cnt;
+                       top->free--;
+                       if (top->free < 0)
+                               return spanhash_rehash(top);
+                       return top;
+               }
+               if (h->hashval == hashval) {
+                       h->cnt += cnt;
+                       return top;
+               }
+               if (lim <= bucket)
+                       bucket = 0;
+       }
+}
+
+static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
+{
+       int i, n;
+       unsigned int accum1, accum2, hashval;
+       struct spanhash_top *hash;
+
+       i = INITIAL_HASH_SIZE;
+       hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i));
+       hash->alloc_log2 = i;
+       hash->free = INITIAL_FREE(i);
+       memset(hash->data, 0, sizeof(struct spanhash) * (1<<i));
+
+       n = 0;
+       accum1 = accum2 = 0;
+       while (sz) {
+               unsigned int c = *buf++;
+               unsigned int old_1 = accum1;
+               sz--;
+               accum1 = (accum1 << 7) ^ (accum2 >> 25);
+               accum2 = (accum2 << 7) ^ (old_1 >> 25);
+               accum1 += c;
+               if (++n < 64 && c != '\n')
+                       continue;
+               hashval = (accum1 + accum2 * 0x61) % HASHBASE;
+               hash = add_spanhash(hash, hashval, n);
+               n = 0;
+               accum1 = accum2 = 0;
+       }
+       return hash;
+}
+
+int diffcore_count_changes(void *src, unsigned long src_size,
+                          void *dst, unsigned long dst_size,
+                          void **src_count_p,
+                          void **dst_count_p,
+                          unsigned long delta_limit,
+                          unsigned long *src_copied,
+                          unsigned long *literal_added)
+{
+       int i, ssz;
+       struct spanhash_top *src_count, *dst_count;
+       unsigned long sc, la;
+
+       src_count = dst_count = NULL;
+       if (src_count_p)
+               src_count = *src_count_p;
+       if (!src_count) {
+               src_count = hash_chars(src, src_size);
+               if (src_count_p)
+                       *src_count_p = src_count;
+       }
+       if (dst_count_p)
+               dst_count = *dst_count_p;
+       if (!dst_count) {
+               dst_count = hash_chars(dst, dst_size);
+               if (dst_count_p)
+                       *dst_count_p = dst_count;
+       }
+       sc = la = 0;
+
+       ssz = 1 << src_count->alloc_log2;
+       for (i = 0; i < ssz; i++) {
+               struct spanhash *s = &(src_count->data[i]);
+               struct spanhash *d;
+               unsigned dst_cnt, src_cnt;
+               if (!s->cnt)
+                       continue;
+               src_cnt = s->cnt;
+               d = spanhash_find(dst_count, s->hashval);
+               dst_cnt = d ? d->cnt : 0;
+               if (src_cnt < dst_cnt) {
+                       la += dst_cnt - src_cnt;
+                       sc += src_cnt;
+               }
+               else
+                       sc += dst_cnt;
+       }
+
+       if (!src_count_p)
+               free(src_count);
+       if (!dst_count_p)
+               free(dst_count);
+       *src_copied = sc;
+       *literal_added = la;
+       return 0;
+}
diff --git a/diffcore-order.c b/diffcore-order.c
new file mode 100644 (file)
index 0000000..0bc2b22
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include <fnmatch.h>
+
+static char **order;
+static int order_cnt;
+
+static void prepare_order(const char *orderfile)
+{
+       int fd, cnt, pass;
+       void *map;
+       char *cp, *endp;
+       struct stat st;
+
+       if (order)
+               return;
+
+       fd = open(orderfile, O_RDONLY);
+       if (fd < 0)
+               return;
+       if (fstat(fd, &st)) {
+               close(fd);
+               return;
+       }
+       map = mmap(NULL, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (map == MAP_FAILED)
+               return;
+       endp = map + st.st_size;
+       for (pass = 0; pass < 2; pass++) {
+               cnt = 0;
+               cp = map;
+               while (cp < endp) {
+                       char *ep;
+                       for (ep = cp; ep < endp && *ep != '\n'; ep++)
+                               ;
+                       /* cp to ep has one line */
+                       if (*cp == '\n' || *cp == '#')
+                               ; /* comment */
+                       else if (pass == 0)
+                               cnt++;
+                       else {
+                               if (*ep == '\n') {
+                                       *ep = 0;
+                                       order[cnt] = cp;
+                               }
+                               else {
+                                       order[cnt] = xmalloc(ep-cp+1);
+                                       memcpy(order[cnt], cp, ep-cp);
+                                       order[cnt][ep-cp] = 0;
+                               }
+                               cnt++;
+                       }
+                       if (ep < endp)
+                               ep++;
+                       cp = ep;
+               }
+               if (pass == 0) {
+                       order_cnt = cnt;
+                       order = xmalloc(sizeof(*order) * cnt);
+               }
+       }
+}
+
+struct pair_order {
+       struct diff_filepair *pair;
+       int orig_order;
+       int order;
+};
+
+static int match_order(const char *path)
+{
+       int i;
+       char p[PATH_MAX];
+
+       for (i = 0; i < order_cnt; i++) {
+               strcpy(p, path);
+               while (p[0]) {
+                       char *cp;
+                       if (!fnmatch(order[i], p, 0))
+                               return i;
+                       cp = strrchr(p, '/');
+                       if (!cp)
+                               break;
+                       *cp = 0;
+               }
+       }
+       return order_cnt;
+}
+
+static int compare_pair_order(const void *a_, const void *b_)
+{
+       struct pair_order const *a, *b;
+       a = (struct pair_order const *)a_;
+       b = (struct pair_order const *)b_;
+       if (a->order != b->order)
+               return a->order - b->order;
+       return a->orig_order - b->orig_order;
+}
+
+void diffcore_order(const char *orderfile)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct pair_order *o;
+       int i;
+
+       if (!q->nr)
+               return;
+
+       o = xmalloc(sizeof(*o) * q->nr);
+       prepare_order(orderfile);
+       for (i = 0; i < q->nr; i++) {
+               o[i].pair = q->queue[i];
+               o[i].orig_order = i;
+               o[i].order = match_order(o[i].pair->two->path);
+       }
+       qsort(o, q->nr, sizeof(*o), compare_pair_order);
+       for (i = 0; i < q->nr; i++)
+               q->queue[i] = o[i].pair;
+       free(o);
+       return;
+}
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
new file mode 100644 (file)
index 0000000..cfcce31
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#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)
+{
+       unsigned int cnt;
+       unsigned long offset, sz;
+       const char *data;
+       if (diff_populate_filespec(one, 0))
+               return 0;
+
+       sz = one->size;
+       data = one->data;
+       cnt = 0;
+
+       if (regexp) {
+               regmatch_t regmatch;
+               int flags = 0;
+
+               while (*data && !regexec(regexp, data, 1, &regmatch, flags)) {
+                       flags |= REG_NOTBOL;
+                       data += regmatch.rm_so;
+                       if (*data) data++;
+                       cnt++;
+               }
+
+       } else { /* Classic exact string match */
+               /* Yes, I've heard of strstr(), but the thing is *data may
+                * not be NUL terminated.  Sue me.
+                */
+               for (offset = 0; offset + len <= sz; offset++) {
+                       /* we count non-overlapping occurrences of needle */
+                       if (!memcmp(needle, data + offset, len)) {
+                               offset += len - 1;
+                               cnt++;
+                       }
+               }
+       }
+       return cnt;
+}
+
+void diffcore_pickaxe(const char *needle, int opts)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       unsigned long len = strlen(needle);
+       int i, has_changes;
+       regex_t regex, *regexp = NULL;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       if (opts & DIFF_PICKAXE_REGEX) {
+               int err;
+               err = regcomp(&regex, needle, REG_EXTENDED | REG_NEWLINE);
+               if (err) {
+                       /* The POSIX.2 people are surely sick */
+                       char errbuf[1024];
+                       regerror(err, &regex, errbuf, 1024);
+                       regfree(&regex);
+                       die("invalid pickaxe regex: %s", errbuf);
+               }
+               regexp = &regex;
+       }
+
+       if (opts & DIFF_PICKAXE_ALL) {
+               /* Showing the whole changeset if needle exists */
+               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (!DIFF_FILE_VALID(p->one)) {
+                               if (!DIFF_FILE_VALID(p->two))
+                                       continue; /* ignore unmerged */
+                               /* created */
+                               if (contains(p->two, needle, len, regexp))
+                                       has_changes++;
+                       }
+                       else if (!DIFF_FILE_VALID(p->two)) {
+                               if (contains(p->one, needle, len, regexp))
+                                       has_changes++;
+                       }
+                       else if (!diff_unmodified_pair(p) &&
+                                contains(p->one, needle, len, regexp) !=
+                                contains(p->two, needle, len, regexp))
+                               has_changes++;
+               }
+               if (has_changes)
+                       return; /* not munge the queue */
+
+               /* otherwise we will clear the whole queue
+                * by copying the empty outq at the end of this
+                * function, but first clear the current entries
+                * in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       }
+       else 
+               /* Showing only the filepairs that has the needle */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       has_changes = 0;
+                       if (!DIFF_FILE_VALID(p->one)) {
+                               if (!DIFF_FILE_VALID(p->two))
+                                       ; /* ignore unmerged */
+                               /* created */
+                               else if (contains(p->two, needle, len, regexp))
+                                       has_changes = 1;
+                       }
+                       else if (!DIFF_FILE_VALID(p->two)) {
+                               if (contains(p->one, needle, len, regexp))
+                                       has_changes = 1;
+                       }
+                       else if (!diff_unmodified_pair(p) &&
+                                contains(p->one, needle, len, regexp) !=
+                                contains(p->two, needle, len, regexp))
+                               has_changes = 1;
+
+                       if (has_changes)
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+
+       if (opts & DIFF_PICKAXE_REGEX) {
+               regfree(&regex);
+       }
+
+       free(q->queue);
+       *q = outq;
+       return;
+}
diff --git a/diffcore-rename.c b/diffcore-rename.c
new file mode 100644 (file)
index 0000000..d57e865
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+/* Table of rename/copy destinations */
+
+static struct diff_rename_dst {
+       struct diff_filespec *two;
+       struct diff_filepair *pair;
+} *rename_dst;
+static int rename_dst_nr, rename_dst_alloc;
+
+static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
+                                                int insert_ok)
+{
+       int first, last;
+
+       first = 0;
+       last = rename_dst_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct diff_rename_dst *dst = &(rename_dst[next]);
+               int cmp = strcmp(two->path, dst->two->path);
+               if (!cmp)
+                       return dst;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       /* not found */
+       if (!insert_ok)
+               return NULL;
+       /* insert to make it at "first" */
+       if (rename_dst_alloc <= rename_dst_nr) {
+               rename_dst_alloc = alloc_nr(rename_dst_alloc);
+               rename_dst = xrealloc(rename_dst,
+                                     rename_dst_alloc * sizeof(*rename_dst));
+       }
+       rename_dst_nr++;
+       if (first < rename_dst_nr)
+               memmove(rename_dst + first + 1, rename_dst + first,
+                       (rename_dst_nr - first - 1) * sizeof(*rename_dst));
+       rename_dst[first].two = alloc_filespec(two->path);
+       fill_filespec(rename_dst[first].two, two->sha1, two->mode);
+       rename_dst[first].pair = NULL;
+       return &(rename_dst[first]);
+}
+
+/* Table of rename/copy src files */
+static struct diff_rename_src {
+       struct diff_filespec *one;
+       unsigned short score; /* to remember the break score */
+       unsigned src_path_left : 1;
+} *rename_src;
+static int rename_src_nr, rename_src_alloc;
+
+static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
+                                                  int src_path_left,
+                                                  unsigned short score)
+{
+       int first, last;
+
+       first = 0;
+       last = rename_src_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct diff_rename_src *src = &(rename_src[next]);
+               int cmp = strcmp(one->path, src->one->path);
+               if (!cmp)
+                       return src;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+
+       /* insert to make it at "first" */
+       if (rename_src_alloc <= rename_src_nr) {
+               rename_src_alloc = alloc_nr(rename_src_alloc);
+               rename_src = xrealloc(rename_src,
+                                     rename_src_alloc * sizeof(*rename_src));
+       }
+       rename_src_nr++;
+       if (first < rename_src_nr)
+               memmove(rename_src + first + 1, rename_src + first,
+                       (rename_src_nr - first - 1) * sizeof(*rename_src));
+       rename_src[first].one = one;
+       rename_src[first].score = score;
+       rename_src[first].src_path_left = src_path_left;
+       return &(rename_src[first]);
+}
+
+static int is_exact_match(struct diff_filespec *src, struct diff_filespec *dst)
+{
+       if (src->sha1_valid && dst->sha1_valid &&
+           !memcmp(src->sha1, dst->sha1, 20))
+               return 1;
+       if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
+               return 0;
+       if (src->size != dst->size)
+               return 0;
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0;
+       if (src->size == dst->size &&
+           !memcmp(src->data, dst->data, src->size))
+               return 1;
+       return 0;
+}
+
+struct diff_score {
+       int src; /* index in rename_src */
+       int dst; /* index in rename_dst */
+       int score;
+};
+
+static int estimate_similarity(struct diff_filespec *src,
+                              struct diff_filespec *dst,
+                              int minimum_score)
+{
+       /* src points at a file that existed in the original tree (or
+        * optionally a file in the destination tree) and dst points
+        * at a newly created file.  They may be quite similar, in which
+        * case we want to say src is renamed to dst or src is copied into
+        * dst, and then some edit has been applied to dst.
+        *
+        * Compare them and return how similar they are, representing
+        * the score as an integer between 0 and MAX_SCORE.
+        *
+        * When there is an exact match, it is considered a better
+        * match than anything else; the destination does not even
+        * call into this function in that case.
+        */
+       unsigned long max_size, delta_size, base_size, src_copied, literal_added;
+       unsigned long delta_limit;
+       int score;
+
+       /* We deal only with regular files.  Symlink renames are handled
+        * only when they are exact matches --- in other words, no edits
+        * after renaming.
+        */
+       if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+               return 0;
+
+       max_size = ((src->size > dst->size) ? src->size : dst->size);
+       base_size = ((src->size < dst->size) ? src->size : dst->size);
+       delta_size = max_size - base_size;
+
+       /* We would not consider edits that change the file size so
+        * drastically.  delta_size must be smaller than
+        * (MAX_SCORE-minimum_score)/MAX_SCORE * min(src->size, dst->size).
+        *
+        * Note that base_size == 0 case is handled here already
+        * and the final score computation below would not have a
+        * divide-by-zero issue.
+        */
+       if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+               return 0;
+
+       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+               return 0; /* error but caught downstream */
+
+
+       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  &src->cnt_data, &dst->cnt_data,
+                                  delta_limit,
+                                  &src_copied, &literal_added))
+               return 0;
+
+       /* How similar are they?
+        * what percentage of material in dst are from source?
+        */
+       if (!dst->size)
+               score = 0; /* should not happen */
+       else
+               score = src_copied * MAX_SCORE / max_size;
+       return score;
+}
+
+static void record_rename_pair(int dst_index, int src_index, int score)
+{
+       struct diff_filespec *one, *two, *src, *dst;
+       struct diff_filepair *dp;
+
+       if (rename_dst[dst_index].pair)
+               die("internal error: dst already matched.");
+
+       src = rename_src[src_index].one;
+       one = alloc_filespec(src->path);
+       fill_filespec(one, src->sha1, src->mode);
+
+       dst = rename_dst[dst_index].two;
+       two = alloc_filespec(dst->path);
+       fill_filespec(two, dst->sha1, dst->mode);
+
+       dp = diff_queue(NULL, one, two);
+       if (!strcmp(src->path, dst->path))
+               dp->score = rename_src[src_index].score;
+       else
+               dp->score = score;
+       dp->source_stays = rename_src[src_index].src_path_left;
+       rename_dst[dst_index].pair = dp;
+}
+
+/*
+ * We sort the rename similarity matrix with the score, in descending
+ * order (the most similar first).
+ */
+static int score_compare(const void *a_, const void *b_)
+{
+       const struct diff_score *a = a_, *b = b_;
+       return b->score - a->score;
+}
+
+static int compute_stays(struct diff_queue_struct *q,
+                        struct diff_filespec *one)
+{
+       int i;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (strcmp(one->path, p->two->path))
+                       continue;
+               if (DIFF_PAIR_RENAME(p)) {
+                       return 0; /* something else is renamed into this */
+               }
+       }
+       return 1;
+}
+
+void diffcore_rename(struct diff_options *options)
+{
+       int detect_rename = options->detect_rename;
+       int minimum_score = options->rename_score;
+       int rename_limit = options->rename_limit;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       struct diff_score *mx;
+       int i, j, rename_count;
+       int num_create, num_src, dst_cnt;
+
+       if (!minimum_score)
+               minimum_score = DEFAULT_RENAME_SCORE;
+       rename_count = 0;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!DIFF_FILE_VALID(p->one))
+                       if (!DIFF_FILE_VALID(p->two))
+                               continue; /* unmerged */
+                       else
+                               locate_rename_dst(p->two, 1);
+               else if (!DIFF_FILE_VALID(p->two)) {
+                       /* If the source is a broken "delete", and
+                        * they did not really want to get broken,
+                        * that means the source actually stays.
+                        */
+                       int stays = (p->broken_pair && !p->score);
+                       register_rename_src(p->one, stays, p->score);
+               }
+               else if (detect_rename == DIFF_DETECT_COPY)
+                       register_rename_src(p->one, 1, p->score);
+       }
+       if (rename_dst_nr == 0 || rename_src_nr == 0 ||
+           (0 < rename_limit && rename_limit < rename_dst_nr))
+               goto cleanup; /* nothing to do */
+
+       /* We really want to cull the candidates list early
+        * with cheap tests in order to avoid doing deltas.
+        */
+       for (i = 0; i < rename_dst_nr; i++) {
+               struct diff_filespec *two = rename_dst[i].two;
+               for (j = 0; j < rename_src_nr; j++) {
+                       struct diff_filespec *one = rename_src[j].one;
+                       if (!is_exact_match(one, two))
+                               continue;
+                       record_rename_pair(i, j, MAX_SCORE);
+                       rename_count++;
+                       break; /* we are done with this entry */
+               }
+       }
+
+       /* Have we run out the created file pool?  If so we can avoid
+        * doing the delta matrix altogether.
+        */
+       if (rename_count == rename_dst_nr)
+               goto cleanup;
+
+       if (minimum_score == MAX_SCORE)
+               goto cleanup;
+
+       num_create = (rename_dst_nr - rename_count);
+       num_src = rename_src_nr;
+       mx = xmalloc(sizeof(*mx) * num_create * num_src);
+       for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
+               int base = dst_cnt * num_src;
+               struct diff_filespec *two = rename_dst[i].two;
+               if (rename_dst[i].pair)
+                       continue; /* dealt with exact match already. */
+               for (j = 0; j < rename_src_nr; j++) {
+                       struct diff_filespec *one = rename_src[j].one;
+                       struct diff_score *m = &mx[base+j];
+                       m->src = j;
+                       m->dst = i;
+                       m->score = estimate_similarity(one, two,
+                                                      minimum_score);
+               }
+               /* We do not need the text anymore */
+               diff_free_filespec_data(two);
+               dst_cnt++;
+       }
+       /* cost matrix sorted by most to least similar pair */
+       qsort(mx, num_create * num_src, sizeof(*mx), score_compare);
+       for (i = 0; i < num_create * num_src; i++) {
+               struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
+               if (dst->pair)
+                       continue; /* already done, either exact or fuzzy. */
+               if (mx[i].score < minimum_score)
+                       break; /* there is no more usable pair. */
+               record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+               rename_count++;
+       }
+       free(mx);
+
+ cleanup:
+       /* At this point, we have found some renames and copies and they
+        * are recorded in rename_dst.  The original list is still in *q.
+        */
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               struct diff_filepair *pair_to_free = NULL;
+
+               if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
+                       /*
+                        * Creation
+                        *
+                        * We would output this create record if it has
+                        * not been turned into a rename/copy already.
+                        */
+                       struct diff_rename_dst *dst =
+                               locate_rename_dst(p->two, 0);
+                       if (dst && dst->pair) {
+                               diff_q(&outq, dst->pair);
+                               pair_to_free = p;
+                       }
+                       else
+                               /* no matching rename/copy source, so
+                                * record this as a creation.
+                                */
+                               diff_q(&outq, p);
+               }
+               else if (DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two)) {
+                       /*
+                        * Deletion
+                        *
+                        * We would output this delete record if:
+                        *
+                        * (1) this is a broken delete and the counterpart
+                        *     broken create remains in the output; or
+                        * (2) this is not a broken delete, and rename_dst
+                        *     does not have a rename/copy to move p->one->path
+                        *     out of existence.
+                        *
+                        * Otherwise, the counterpart broken create
+                        * has been turned into a rename-edit; or
+                        * delete did not have a matching create to
+                        * begin with.
+                        */
+                       if (DIFF_PAIR_BROKEN(p)) {
+                               /* broken delete */
+                               struct diff_rename_dst *dst =
+                                       locate_rename_dst(p->one, 0);
+                               if (dst && dst->pair)
+                                       /* counterpart is now rename/copy */
+                                       pair_to_free = p;
+                       }
+                       else {
+                               for (j = 0; j < rename_dst_nr; j++) {
+                                       if (!rename_dst[j].pair)
+                                               continue;
+                                       if (strcmp(rename_dst[j].pair->
+                                                  one->path,
+                                                  p->one->path))
+                                               continue;
+                                       break;
+                               }
+                               if (j < rename_dst_nr)
+                                       /* this path remains */
+                                       pair_to_free = p;
+                       }
+
+                       if (pair_to_free)
+                               ;
+                       else
+                               diff_q(&outq, p);
+               }
+               else if (!diff_unmodified_pair(p))
+                       /* all the usual ones need to be kept */
+                       diff_q(&outq, p);
+               else
+                       /* no need to keep unmodified pairs */
+                       pair_to_free = p;
+
+               if (pair_to_free)
+                       diff_free_filepair(pair_to_free);
+       }
+       diff_debug_queue("done copying original", &outq);
+
+       free(q->queue);
+       *q = outq;
+       diff_debug_queue("done collapsing", q);
+
+       /* We need to see which rename source really stays here;
+        * earlier we only checked if the path is left in the result,
+        * but even if a path remains in the result, if that is coming
+        * from copying something else on top of it, then the original
+        * source is lost and does not stay.
+        */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (DIFF_PAIR_RENAME(p) && p->source_stays) {
+                       /* If one appears as the target of a rename-copy,
+                        * then mark p->source_stays = 0; otherwise
+                        * leave it as is.
+                        */
+                       p->source_stays = compute_stays(q, p->one);
+               }
+       }
+
+       for (i = 0; i < rename_dst_nr; i++) {
+               diff_free_filespec_data(rename_dst[i].two);
+               free(rename_dst[i].two);
+       }
+
+       free(rename_dst);
+       rename_dst = NULL;
+       rename_dst_nr = rename_dst_alloc = 0;
+       free(rename_src);
+       rename_src = NULL;
+       rename_src_nr = rename_src_alloc = 0;
+       return;
+}
diff --git a/diffcore.h b/diffcore.h
new file mode 100644 (file)
index 0000000..73c7842
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef _DIFFCORE_H_
+#define _DIFFCORE_H_
+
+/* This header file is internal between diff.c and its diff transformers
+ * (e.g. diffcore-rename, diffcore-pickaxe).  Never include this header
+ * in anything else.
+ */
+
+/* We internally use unsigned short as the score value,
+ * and rely on an int capable to hold 32-bits.  -B can take
+ * -Bmerge_score/break_score format and the two scores are
+ * passed around in one int (high 16-bit for merge and low 16-bit
+ * for break).
+ */
+#define MAX_SCORE 60000.0
+#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
+#define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%) */
+#define DEFAULT_MERGE_SCORE  36000 /* maximum for break-merge to happen 60%) */
+
+#define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
+
+struct diff_filespec {
+       unsigned char sha1[20];
+       char *path;
+       void *data;
+       void *cnt_data;
+       unsigned long size;
+       int xfrm_flags;          /* for use by the xfrm */
+       unsigned short mode;     /* file mode */
+       unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
+                                 * if false, use the name and read from
+                                 * the filesystem.
+                                 */
+#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
+       unsigned should_free : 1; /* data should be free()'ed */
+       unsigned should_munmap : 1; /* data should be munmap()'ed */
+};
+
+extern struct diff_filespec *alloc_filespec(const char *);
+extern void fill_filespec(struct diff_filespec *, const unsigned char *,
+                         unsigned short);
+
+extern int diff_populate_filespec(struct diff_filespec *, int);
+extern void diff_free_filespec_data(struct diff_filespec *);
+
+struct diff_filepair {
+       struct diff_filespec *one;
+       struct diff_filespec *two;
+       unsigned short int score;
+       char status; /* M C R N D U (see Documentation/diff-format.txt) */
+       unsigned source_stays : 1; /* all of R/C are copies */
+       unsigned broken_pair : 1;
+};
+#define DIFF_PAIR_UNMERGED(p) \
+       (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two))
+
+#define DIFF_PAIR_RENAME(p) (strcmp((p)->one->path, (p)->two->path))
+
+#define DIFF_PAIR_BROKEN(p) \
+       ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \
+         ((p)->broken_pair != 0) )
+
+#define DIFF_PAIR_TYPE_CHANGED(p) \
+       ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode))
+
+#define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode)
+
+extern void diff_free_filepair(struct diff_filepair *);
+
+extern int diff_unmodified_pair(struct diff_filepair *);
+
+struct diff_queue_struct {
+       struct diff_filepair **queue;
+       int alloc;
+       int nr;
+};
+
+extern struct diff_queue_struct diff_queued_diff;
+extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
+                                       struct diff_filespec *,
+                                       struct diff_filespec *);
+extern void diff_q(struct diff_queue_struct *, struct diff_filepair *);
+
+extern void diffcore_pathspec(const char **pathspec);
+extern void diffcore_break(int);
+extern void diffcore_rename(struct diff_options *);
+extern void diffcore_merge_broken(void);
+extern void diffcore_pickaxe(const char *needle, int opts);
+extern void diffcore_order(const char *orderfile);
+
+#define DIFF_DEBUG 0
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *, int, const char *);
+void diff_debug_filepair(const struct diff_filepair *, int);
+void diff_debug_queue(const char *, struct diff_queue_struct *);
+#else
+#define diff_debug_filespec(a,b,c) do {} while(0)
+#define diff_debug_filepair(a,b) do {} while(0)
+#define diff_debug_queue(a,b) do {} while(0)
+#endif
+
+extern int diffcore_count_changes(void *src, unsigned long src_size,
+                                 void *dst, unsigned long dst_size,
+                                 void **src_count_p,
+                                 void **dst_count_p,
+                                 unsigned long delta_limit,
+                                 unsigned long *src_copied,
+                                 unsigned long *literal_added);
+
+#endif
diff --git a/dir.c b/dir.c
new file mode 100644 (file)
index 0000000..d778ecd
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,401 @@
+/*
+ * This handles recursive filename detection with exclude
+ * files, index knowledge etc..
+ *
+ * Copyright (C) Linus Torvalds, 2005-2006
+ *              Junio Hamano, 2005-2006
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "dir.h"
+
+int common_prefix(const char **pathspec)
+{
+       const char *path, *slash, *next;
+       int prefix;
+
+       if (!pathspec)
+               return 0;
+
+       path = *pathspec;
+       slash = strrchr(path, '/');
+       if (!slash)
+               return 0;
+
+       prefix = slash - path + 1;
+       while ((next = *++pathspec) != NULL) {
+               int len = strlen(next);
+               if (len >= prefix && !memcmp(path, next, len))
+                       continue;
+               for (;;) {
+                       if (!len)
+                               return 0;
+                       if (next[--len] != '/')
+                               continue;
+                       if (memcmp(path, next, len+1))
+                               continue;
+                       prefix = len + 1;
+                       break;
+               }
+       }
+       return prefix;
+}
+
+static int match_one(const char *match, const char *name, int namelen)
+{
+       int matchlen;
+
+       /* If the match was just the prefix, we matched */
+       matchlen = strlen(match);
+       if (!matchlen)
+               return 1;
+
+       /*
+        * If we don't match the matchstring exactly,
+        * we need to match by fnmatch
+        */
+       if (strncmp(match, name, matchlen))
+               return !fnmatch(match, name, 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];
+}
+
+int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
+{
+       int retval;
+       const char *match;
+
+       name += prefix;
+       namelen -= prefix;
+
+       for (retval = 0; (match = *pathspec++) != NULL; seen++) {
+               if (retval & *seen)
+                       continue;
+               match += prefix;
+               if (match_one(match, name, namelen)) {
+                       retval = 1;
+                       *seen = 1;
+               }
+       }
+       return retval;
+}
+
+void add_exclude(const char *string, const char *base,
+                int baselen, struct exclude_list *which)
+{
+       struct exclude *x = xmalloc(sizeof (*x));
+
+       x->pattern = string;
+       x->base = base;
+       x->baselen = baselen;
+       if (which->nr == which->alloc) {
+               which->alloc = alloc_nr(which->alloc);
+               which->excludes = realloc(which->excludes,
+                                         which->alloc * sizeof(x));
+       }
+       which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+                                   const char *base,
+                                   int baselen,
+                                   struct exclude_list *which)
+{
+       int fd, i;
+       long size;
+       char *buf, *entry;
+
+       fd = open(fname, O_RDONLY);
+       if (fd < 0)
+               goto err;
+       size = lseek(fd, 0, SEEK_END);
+       if (size < 0)
+               goto err;
+       lseek(fd, 0, SEEK_SET);
+       if (size == 0) {
+               close(fd);
+               return 0;
+       }
+       buf = xmalloc(size+1);
+       if (read(fd, buf, size) != size)
+               goto err;
+       close(fd);
+
+       buf[size++] = '\n';
+       entry = buf;
+       for (i = 0; i < size; i++) {
+               if (buf[i] == '\n') {
+                       if (entry != buf + i && entry[0] != '#') {
+                               buf[i - (i && buf[i-1] == '\r')] = 0;
+                               add_exclude(entry, base, baselen, which);
+                       }
+                       entry = buf + i + 1;
+               }
+       }
+       return 0;
+
+ err:
+       if (0 <= fd)
+               close(fd);
+       return -1;
+}
+
+void add_excludes_from_file(struct dir_struct *dir, const char *fname)
+{
+       if (add_excludes_from_file_1(fname, "", 0,
+                                    &dir->exclude_list[EXC_FILE]) < 0)
+               die("cannot use %s as an exclude file", fname);
+}
+
+static 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];
+       int current_nr = el->nr;
+
+       if (dir->exclude_per_dir) {
+               memcpy(exclude_file, base, baselen);
+               strcpy(exclude_file + baselen, dir->exclude_per_dir);
+               add_excludes_from_file_1(exclude_file, base, baselen, el);
+       }
+       return current_nr;
+}
+
+static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+{
+       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+
+       while (stk < el->nr)
+               free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+                     int pathlen,
+                     struct exclude_list *el)
+{
+       int i;
+
+       if (el->nr) {
+               for (i = el->nr - 1; 0 <= i; i--) {
+                       struct exclude *x = el->excludes[i];
+                       const char *exclude = x->pattern;
+                       int to_exclude = 1;
+
+                       if (*exclude == '!') {
+                               to_exclude = 0;
+                               exclude++;
+                       }
+
+                       if (!strchr(exclude, '/')) {
+                               /* match basename */
+                               const char *basename = strrchr(pathname, '/');
+                               basename = (basename) ? basename+1 : pathname;
+                               if (fnmatch(exclude, basename, 0) == 0)
+                                       return to_exclude;
+                       }
+                       else {
+                               /* match with FNM_PATHNAME:
+                                * exclude has base (baselen long) implicitly
+                                * in front of it.
+                                */
+                               int baselen = x->baselen;
+                               if (*exclude == '/')
+                                       exclude++;
+
+                               if (pathlen < baselen ||
+                                   (baselen && pathname[baselen-1] != '/') ||
+                                   strncmp(pathname, x->base, baselen))
+                                   continue;
+
+                               if (fnmatch(exclude, pathname+baselen,
+                                           FNM_PATHNAME) == 0)
+                                       return to_exclude;
+                       }
+               }
+       }
+       return -1; /* undecided */
+}
+
+int excluded(struct dir_struct *dir, const char *pathname)
+{
+       int pathlen = strlen(pathname);
+       int st;
+
+       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+               switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+               case 0:
+                       return 0;
+               case 1:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static void add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+       struct dir_entry *ent;
+
+       if (cache_name_pos(pathname, len) >= 0)
+               return;
+
+       if (dir->nr == dir->alloc) {
+               int alloc = alloc_nr(dir->alloc);
+               dir->alloc = alloc;
+               dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
+       }
+       ent = xmalloc(sizeof(*ent) + len + 1);
+       ent->len = len;
+       memcpy(ent->name, pathname, len);
+       ent->name[len] = 0;
+       dir->entries[dir->nr++] = ent;
+}
+
+static int dir_exists(const char *dirname, int len)
+{
+       int pos = cache_name_pos(dirname, len);
+       if (pos >= 0)
+               return 1;
+       pos = -pos-1;
+       if (pos >= active_nr) /* can't */
+               return 0;
+       return !strncmp(active_cache[pos]->name, dirname, len);
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       DIR *fdir = opendir(path);
+       int contents = 0;
+
+       if (fdir) {
+               int exclude_stk;
+               struct dirent *de;
+               char fullname[MAXPATHLEN + 1];
+               memcpy(fullname, base, baselen);
+
+               exclude_stk = push_exclude_per_directory(dir, base, baselen);
+
+               while ((de = readdir(fdir)) != NULL) {
+                       int len;
+
+                       if ((de->d_name[0] == '.') &&
+                           (de->d_name[1] == 0 ||
+                            !strcmp(de->d_name + 1, ".") ||
+                            !strcmp(de->d_name + 1, "git")))
+                               continue;
+                       len = strlen(de->d_name);
+                       memcpy(fullname + baselen, de->d_name, len+1);
+                       if (excluded(dir, fullname) != dir->show_ignored) {
+                               if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
+                                       continue;
+                               }
+                       }
+
+                       switch (DTYPE(de)) {
+                       struct stat st;
+                       int subdir, rewind_base;
+                       default:
+                               continue;
+                       case DT_UNKNOWN:
+                               if (lstat(fullname, &st))
+                                       continue;
+                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+                                       break;
+                               if (!S_ISDIR(st.st_mode))
+                                       continue;
+                               /* fallthrough */
+                       case DT_DIR:
+                               memcpy(fullname + baselen + len, "/", 2);
+                               len++;
+                               rewind_base = dir->nr;
+                               subdir = read_directory_recursive(dir, fullname, fullname,
+                                                       baselen + len);
+                               if (dir->show_other_directories &&
+                                   (subdir || !dir->hide_empty_directories) &&
+                                   !dir_exists(fullname, baselen + len)) {
+                                       // Rewind the read subdirectory
+                                       while (dir->nr > rewind_base)
+                                               free(dir->entries[--dir->nr]);
+                                       break;
+                               }
+                               contents += subdir;
+                               continue;
+                       case DT_REG:
+                       case DT_LNK:
+                               break;
+                       }
+                       add_name(dir, fullname, baselen + len);
+                       contents++;
+               }
+               closedir(fdir);
+
+               pop_exclude_per_directory(dir, exclude_stk);
+       }
+
+       return contents;
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+       const struct dir_entry *e1 = *(const struct dir_entry **)p1;
+       const struct dir_entry *e2 = *(const struct dir_entry **)p2;
+
+       return cache_name_compare(e1->name, e1->len,
+                                 e2->name, e2->len);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+{
+       /*
+        * Make sure to do the per-directory exclude for all the
+        * directories leading up to our base.
+        */
+       if (baselen) {
+               if (dir->exclude_per_dir) {
+                       char *p, *pp = xmalloc(baselen+1);
+                       memcpy(pp, base, baselen+1);
+                       p = pp;
+                       while (1) {
+                               char save = *p;
+                               *p = 0;
+                               push_exclude_per_directory(dir, pp, p-pp);
+                               *p++ = save;
+                               if (!save)
+                                       break;
+                               p = strchr(p, '/');
+                               if (p)
+                                       p++;
+                               else
+                                       p = pp + baselen;
+                       }
+                       free(pp);
+               }
+       }
+
+       read_directory_recursive(dir, path, base, baselen);
+       qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+       return dir->nr;
+}
diff --git a/dir.h b/dir.h
new file mode 100644 (file)
index 0000000..56a1b7f
--- /dev/null
+++ b/dir.h
@@ -0,0 +1,51 @@
+#ifndef DIR_H
+#define DIR_H
+
+/*
+ * We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+
+struct dir_entry {
+       int len;
+       char name[FLEX_ARRAY]; /* more */
+};
+
+struct exclude_list {
+       int nr;
+       int alloc;
+       struct exclude {
+               const char *pattern;
+               const char *base;
+               int baselen;
+       } **excludes;
+};
+
+struct dir_struct {
+       int nr, alloc;
+       unsigned int show_ignored:1,
+                    show_other_directories:1,
+                    hide_empty_directories:1;
+       struct dir_entry **entries;
+
+       /* Exclude info */
+       const char *exclude_per_dir;
+       struct exclude_list exclude_list[3];
+};
+
+extern int common_prefix(const char **pathspec);
+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 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);
+
+#endif
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
new file mode 100644 (file)
index 0000000..1ccaf51
--- /dev/null
@@ -0,0 +1,64 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+       if (it->entry_count < 0)
+               printf("%-40s %s%s (%d subtrees)\n",
+                      "invalid", x, pfx, it->subtree_nr);
+       else
+               printf("%s %s%s (%d entries, %d subtrees)\n",
+                      sha1_to_hex(it->sha1), x, pfx,
+                      it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+                          struct cache_tree *ref,
+                          const char *pfx)
+{
+       int i;
+       int errs = 0;
+
+       if (!it || !ref)
+               /* missing in either */
+               return 0;
+
+       if (it->entry_count < 0) {
+               dump_one(it, pfx, "");
+               dump_one(ref, pfx, "#(ref) ");
+               if (it->subtree_nr != ref->subtree_nr)
+                       errs = 1;
+       }
+       else {
+               dump_one(it, pfx, "");
+               if (memcmp(it->sha1, ref->sha1, 20) ||
+                   ref->entry_count != it->entry_count ||
+                   ref->subtree_nr != it->subtree_nr) {
+                       dump_one(ref, pfx, "#(ref) ");
+                       errs = 1;
+               }
+       }
+
+       for (i = 0; i < it->subtree_nr; i++) {
+               char path[PATH_MAX];
+               struct cache_tree_sub *down = it->down[i];
+               struct cache_tree_sub *rdwn;
+
+               rdwn = cache_tree_sub(ref, down->name);
+               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+                       errs = 1;
+       }
+       return errs;
+}
+
+int main(int ac, char **av)
+{
+       struct cache_tree *another = cache_tree();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       return dump_cache_tree(active_cache_tree, another, "");
+}
diff --git a/entry.c b/entry.c
new file mode 100644 (file)
index 0000000..793724f
--- /dev/null
+++ b/entry.c
@@ -0,0 +1,176 @@
+#include <sys/types.h>
+#include <dirent.h>
+#include "cache.h"
+#include "blob.h"
+
+static void create_directories(const char *path, struct checkout *state)
+{
+       int len = strlen(path);
+       char *buf = xmalloc(len + 1);
+       const char *slash = path;
+
+       while ((slash = strchr(slash+1, '/')) != NULL) {
+               len = slash - path;
+               memcpy(buf, path, len);
+               buf[len] = 0;
+               if (mkdir(buf, 0777)) {
+                       if (errno == EEXIST) {
+                               struct stat st;
+                               if (len > state->base_dir_len && state->force && !unlink(buf) && !mkdir(buf, 0777))
+                                       continue;
+                               if (!stat(buf, &st) && S_ISDIR(st.st_mode))
+                                       continue; /* ok */
+                       }
+                       die("cannot create directory at %s", buf);
+               }
+       }
+       free(buf);
+}
+
+static void remove_subtree(const char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+       char pathbuf[PATH_MAX];
+       char *name;
+       
+       if (!dir)
+               die("cannot opendir %s", path);
+       strcpy(pathbuf, path);
+       name = pathbuf + strlen(path);
+       *name++ = '/';
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st;
+               if ((de->d_name[0] == '.') &&
+                   ((de->d_name[1] == 0) ||
+                    ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+                       continue;
+               strcpy(name, de->d_name);
+               if (lstat(pathbuf, &st))
+                       die("cannot lstat %s", pathbuf);
+               if (S_ISDIR(st.st_mode))
+                       remove_subtree(pathbuf);
+               else if (unlink(pathbuf))
+                       die("cannot unlink %s", pathbuf);
+       }
+       closedir(dir);
+       if (rmdir(path))
+               die("cannot rmdir %s", path);
+}
+
+static int create_file(const char *path, unsigned int mode)
+{
+       mode = (mode & 0100) ? 0777 : 0666;
+       return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
+}
+
+static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)
+{
+       int fd;
+       void *new;
+       unsigned long size;
+       long wrote;
+       char type[20];
+
+       new = read_sha1_file(ce->sha1, type, &size);
+       if (!new || strcmp(type, blob_type)) {
+               if (new)
+                       free(new);
+               return error("git-checkout-index: unable to read sha1 file of %s (%s)",
+                       path, sha1_to_hex(ce->sha1));
+       }
+       switch (ntohl(ce->ce_mode) & S_IFMT) {
+       case S_IFREG:
+               if (to_tempfile) {
+                       strcpy(path, ".merge_file_XXXXXX");
+                       fd = mkstemp(path);
+               } else
+                       fd = create_file(path, ntohl(ce->ce_mode));
+               if (fd < 0) {
+                       free(new);
+                       return error("git-checkout-index: unable to create file %s (%s)",
+                               path, strerror(errno));
+               }
+               wrote = write(fd, new, size);
+               close(fd);
+               free(new);
+               if (wrote != size)
+                       return error("git-checkout-index: unable to write file %s", path);
+               break;
+       case S_IFLNK:
+               if (to_tempfile) {
+                       strcpy(path, ".merge_link_XXXXXX");
+                       fd = mkstemp(path);
+                       if (fd < 0) {
+                               free(new);
+                               return error("git-checkout-index: unable to create "
+                                                "file %s (%s)", path, strerror(errno));
+                       }
+                       wrote = write(fd, new, size);
+                       close(fd);
+                       free(new);
+                       if (wrote != size)
+                               return error("git-checkout-index: unable to write file %s",
+                                       path);
+               } else {
+                       wrote = symlink(new, path);
+                       free(new);
+                       if (wrote)
+                               return error("git-checkout-index: unable to create "
+                                                "symlink %s (%s)", path, strerror(errno));
+               }
+               break;
+       default:
+               free(new);
+               return error("git-checkout-index: unknown file mode for %s", path);
+       }
+
+       if (state->refresh_cache) {
+               struct stat st;
+               lstat(ce->name, &st);
+               fill_stat_cache_info(ce, &st);
+       }
+       return 0;
+}
+
+int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)
+{
+       static char path[MAXPATHLEN+1];
+       struct stat st;
+       int len = state->base_dir_len;
+
+       if (topath)
+               return write_entry(ce, topath, state, 1);
+
+       memcpy(path, state->base_dir, len);
+       strcpy(path + len, ce->name);
+
+       if (!lstat(path, &st)) {
+               unsigned changed = ce_match_stat(ce, &st, 1);
+               if (!changed)
+                       return 0;
+               if (!state->force) {
+                       if (!state->quiet)
+                               fprintf(stderr, "git-checkout-index: %s already exists\n", path);
+                       return -1;
+               }
+
+               /*
+                * We unlink the old file, to get the new one with the
+                * right permissions (including umask, which is nasty
+                * to emulate by hand - much easier to let the system
+                * just do the right thing)
+                */
+               unlink(path);
+               if (S_ISDIR(st.st_mode)) {
+                       if (!state->force)
+                               return error("%s is a directory", path);
+                       remove_subtree(path);
+               }
+       } else if (state->not_new)
+               return 0;
+       create_directories(path, state);
+       return write_entry(ce, path, state, 0);
+}
+
+
diff --git a/environment.c b/environment.c
new file mode 100644 (file)
index 0000000..2e79eab
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * We put all the git config variables in this same object
+ * file, so that programs can link against the config parser
+ * without having to link against all the rest of git.
+ *
+ * In particular, no need to bring in libz etc unless needed,
+ * even if you might want to know where the git directory etc
+ * are.
+ */
+#include "cache.h"
+
+char git_default_email[MAX_GITNAME];
+char git_default_name[MAX_GITNAME];
+int trust_executable_bit = 1;
+int assume_unchanged = 0;
+int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
+int warn_ambiguous_refs = 1;
+int repository_format_version = 0;
+char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
+int shared_repository = 0;
+const char *apply_default_whitespace = NULL;
+
+static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
+       *git_graft_file;
+static void setup_git_env(void)
+{
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
+       if (!git_dir)
+               git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+       git_object_dir = getenv(DB_ENVIRONMENT);
+       if (!git_object_dir) {
+               git_object_dir = xmalloc(strlen(git_dir) + 9);
+               sprintf(git_object_dir, "%s/objects", git_dir);
+       }
+       git_refs_dir = xmalloc(strlen(git_dir) + 6);
+       sprintf(git_refs_dir, "%s/refs", git_dir);
+       git_index_file = getenv(INDEX_ENVIRONMENT);
+       if (!git_index_file) {
+               git_index_file = xmalloc(strlen(git_dir) + 7);
+               sprintf(git_index_file, "%s/index", git_dir);
+       }
+       git_graft_file = getenv(GRAFT_ENVIRONMENT);
+       if (!git_graft_file)
+               git_graft_file = strdup(git_path("info/grafts"));
+}
+
+char *get_git_dir(void)
+{
+       if (!git_dir)
+               setup_git_env();
+       return git_dir;
+}
+
+char *get_object_directory(void)
+{
+       if (!git_object_dir)
+               setup_git_env();
+       return git_object_dir;
+}
+
+char *get_refs_directory(void)
+{
+       if (!git_refs_dir)
+               setup_git_env();
+       return git_refs_dir;
+}
+
+char *get_index_file(void)
+{
+       if (!git_index_file)
+               setup_git_env();
+       return git_index_file;
+}
+
+char *get_graft_file(void)
+{
+       if (!git_graft_file)
+               setup_git_env();
+       return git_graft_file;
+}
+
+
diff --git a/exec_cmd.c b/exec_cmd.c
new file mode 100644 (file)
index 0000000..c1539d1
--- /dev/null
@@ -0,0 +1,130 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#define MAX_ARGS       32
+
+extern char **environ;
+static const char *builtin_exec_path = GIT_EXEC_PATH;
+static const char *current_exec_path = NULL;
+
+void git_set_exec_path(const char *exec_path)
+{
+       current_exec_path = exec_path;
+}
+
+
+/* Returns the highest-priority, location to look for git programs. */
+const char *git_exec_path(void)
+{
+       const char *env;
+
+       if (current_exec_path)
+               return current_exec_path;
+
+       env = getenv("GIT_EXEC_PATH");
+       if (env && *env) {
+               return env;
+       }
+
+       return builtin_exec_path;
+}
+
+
+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"),
+                               builtin_exec_path };
+
+       for (i = 0; i < ARRAY_SIZE(paths); ++i) {
+               size_t len;
+               int rc;
+               const char *exec_dir = paths[i];
+               const char *tmp;
+
+               if (!exec_dir || !*exec_dir) continue;
+
+               if (*exec_dir != '/') {
+                       if (!getcwd(git_command, sizeof(git_command))) {
+                               fprintf(stderr, "git: cannot determine "
+                                       "current directory: %s\n",
+                                       strerror(errno));
+                               break;
+                       }
+                       len = strlen(git_command);
+
+                       /* Trivial cleanup */
+                       while (!strncmp(exec_dir, "./", 2)) {
+                               exec_dir += 2;
+                               while (*exec_dir == '/')
+                                       exec_dir++;
+                       }
+
+                       rc = snprintf(git_command + len,
+                                     sizeof(git_command) - len, "/%s",
+                                     exec_dir);
+                       if (rc < 0 || rc >= sizeof(git_command) - len) {
+                               fprintf(stderr, "git: command name given "
+                                       "is too long.\n");
+                               break;
+                       }
+               } else {
+                       if (strlen(exec_dir) + 1 > sizeof(git_command)) {
+                               fprintf(stderr, "git: command name given "
+                                       "is too long.\n");
+                               break;
+                       }
+                       strcpy(git_command, exec_dir);
+               }
+
+               len = strlen(git_command);
+               rc = snprintf(git_command + len, sizeof(git_command) - len,
+                             "/git-%s", argv[0]);
+               if (rc < 0 || rc >= sizeof(git_command) - len) {
+                       fprintf(stderr,
+                               "git: command name given is too long.\n");
+                       break;
+               }
+
+               /* argv[0] must be the git command, but the argv array
+                * belongs to the caller, and my be reused in
+                * subsequent loop iterations. Save argv[0] and
+                * restore it on error.
+                */
+
+               tmp = argv[0];
+               argv[0] = git_command;
+
+               /* execve() can only ever return if it fails */
+               execve(git_command, (char **)argv, environ);
+
+               argv[0] = tmp;
+       }
+       return -1;
+
+}
+
+
+int execl_git_cmd(const char *cmd,...)
+{
+       int argc;
+       const char *argv[MAX_ARGS + 1];
+       const char *arg;
+       va_list param;
+
+       va_start(param, cmd);
+       argv[0] = cmd;
+       argc = 1;
+       while (argc < MAX_ARGS) {
+               arg = argv[argc++] = va_arg(param, char *);
+               if (!arg)
+                       break;
+       }
+       va_end(param);
+       if (MAX_ARGS <= argc)
+               return error("too many args to run %s", cmd);
+
+       argv[argc] = NULL;
+       return execv_git_cmd(argv);
+}
diff --git a/exec_cmd.h b/exec_cmd.h
new file mode 100644 (file)
index 0000000..989621f
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef __GIT_EXEC_CMD_H_
+#define __GIT_EXEC_CMD_H_
+
+extern void git_set_exec_path(const char *exec_path);
+extern const char* git_exec_path(void);
+extern int execv_git_cmd(const char **argv); /* NULL terminated */
+extern int execl_git_cmd(const char *cmd, ...);
+
+
+#endif /* __GIT_EXEC_CMD_H_ */
diff --git a/fetch-clone.c b/fetch-clone.c
new file mode 100644 (file)
index 0000000..da1b3ff
--- /dev/null
@@ -0,0 +1,238 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#include <sys/wait.h>
+#include <sys/time.h>
+
+static int finish_pack(const char *pack_tmp_name, const char *me)
+{
+       int pipe_fd[2];
+       pid_t pid;
+       char idx[PATH_MAX];
+       char final[PATH_MAX];
+       char hash[41];
+       unsigned char sha1[20];
+       char *cp;
+       int err = 0;
+
+       if (pipe(pipe_fd) < 0)
+               die("%s: unable to set up pipe", me);
+
+       strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
+       cp = strrchr(idx, '/');
+       memcpy(cp, "/pidx", 5);
+
+       pid = fork();
+       if (pid < 0)
+               die("git-clone-pack: unable to fork off git-index-pack");
+       if (!pid) {
+               close(0);
+               dup2(pipe_fd[1], 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               execl_git_cmd("index-pack", "-o", idx, pack_tmp_name, NULL);
+               error("cannot exec git-index-pack <%s> <%s>",
+                     idx, pack_tmp_name);
+               exit(1);
+       }
+       close(pipe_fd[1]);
+       if (read(pipe_fd[0], hash, 40) != 40) {
+               error("%s: unable to read from git-index-pack", me);
+               err = 1;
+       }
+       close(pipe_fd[0]);
+
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       error("waitpid failed (%s)", strerror(errno));
+                       goto error_die;
+               }
+               if (WIFSIGNALED(status)) {
+                       int sig = WTERMSIG(status);
+                       error("git-index-pack died of signal %d", sig);
+                       goto error_die;
+               }
+               if (!WIFEXITED(status)) {
+                       error("git-index-pack died of unnatural causes %d",
+                             status);
+                       goto error_die;
+               }
+               code = WEXITSTATUS(status);
+               if (code) {
+                       error("git-index-pack died with error code %d", code);
+                       goto error_die;
+               }
+               if (err)
+                       goto error_die;
+               break;
+       }
+       hash[40] = 0;
+       if (get_sha1_hex(hash, sha1)) {
+               error("git-index-pack reported nonsense '%s'", hash);
+               goto error_die;
+       }
+       /* Now we have pack in pack_tmp_name[], and
+        * idx in idx[]; rename them to their final names.
+        */
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.pack", get_object_directory(), hash);
+       move_temp_to_file(pack_tmp_name, final);
+       chmod(final, 0444);
+       snprintf(final, sizeof(final),
+                "%s/pack/pack-%s.idx", get_object_directory(), hash);
+       move_temp_to_file(idx, final);
+       chmod(final, 0444);
+       return 0;
+
+ error_die:
+       unlink(idx);
+       unlink(pack_tmp_name);
+       exit(1);
+}
+
+int receive_unpack_pack(int fd[2], const char *me, int quiet)
+{
+       int status;
+       pid_t pid;
+
+       pid = fork();
+       if (pid < 0)
+               die("%s: unable to fork off git-unpack-objects", me);
+       if (!pid) {
+               dup2(fd[0], 0);
+               close(fd[0]);
+               close(fd[1]);
+               execl_git_cmd("unpack-objects", quiet ? "-q" : NULL, NULL);
+               die("git-unpack-objects exec failed");
+       }
+       close(fd[0]);
+       close(fd[1]);
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno != EINTR)
+                       die("waiting for git-unpack-objects: %s",
+                           strerror(errno));
+       }
+       if (WIFEXITED(status)) {
+               int code = WEXITSTATUS(status);
+               if (code)
+                       die("git-unpack-objects died with error code %d",
+                           code);
+               return 0;
+       }
+       if (WIFSIGNALED(status)) {
+               int sig = WTERMSIG(status);
+               die("git-unpack-objects died of signal %d", sig);
+       }
+       die("git-unpack-objects died of unnatural causes %d", status);
+}
+
+/*
+ * We average out the download speed over this many "events", where
+ * an event is a minimum of about half a second. That way, we get
+ * a reasonably stable number.
+ */
+#define NR_AVERAGE (4)
+
+/*
+ * A "binary msec" is a power-of-two-msec, aka 1/1024th of a second.
+ * Keeing the time in that format means that "bytes / msecs" means
+ * is the same as kB/s (modulo rounding).
+ *
+ * 1000512 is a magic number (usecs in a second, rounded up by half
+ * of 1024, to make "rounding" come out right ;)
+ */
+#define usec_to_binarymsec(x) ((int)(x) / (1000512 >> 10))
+
+int receive_keep_pack(int fd[2], const char *me, int quiet)
+{
+       char tmpfile[PATH_MAX];
+       int ofd, ifd;
+       unsigned long total;
+       static struct timeval prev_tv;
+       struct average {
+               unsigned long bytes;
+               unsigned long time;
+       } download[NR_AVERAGE] = { {0, 0}, };
+       unsigned long avg_bytes, avg_time;
+       int idx = 0;
+
+       ifd = fd[0];
+       snprintf(tmpfile, sizeof(tmpfile),
+                "%s/pack/tmp-XXXXXX", get_object_directory());
+       ofd = mkstemp(tmpfile);
+       if (ofd < 0)
+               return error("unable to create temporary file %s", tmpfile);
+
+       gettimeofday(&prev_tv, NULL);
+       total = 0;
+       avg_bytes = 0;
+       avg_time = 0;
+       while (1) {
+               char buf[8192];
+               ssize_t sz, wsz, pos;
+               sz = read(ifd, buf, sizeof(buf));
+               if (sz == 0)
+                       break;
+               if (sz < 0) {
+                       if (errno != EINTR && errno != EAGAIN) {
+                               error("error reading pack (%s)", strerror(errno));
+                               close(ofd);
+                               unlink(tmpfile);
+                               return -1;
+                       }
+                       sz = 0;
+               }
+               pos = 0;
+               while (pos < sz) {
+                       wsz = write(ofd, buf + pos, sz - pos);
+                       if (wsz < 0) {
+                               error("error writing pack (%s)",
+                                     strerror(errno));
+                               close(ofd);
+                               unlink(tmpfile);
+                               return -1;
+                       }
+                       pos += wsz;
+               }
+               total += sz;
+               if (!quiet) {
+                       static unsigned long last;
+                       struct timeval tv;
+                       unsigned long diff = total - last;
+                       /* not really "msecs", but a power-of-two millisec (1/1024th of a sec) */
+                       unsigned long msecs;
+
+                       gettimeofday(&tv, NULL);
+                       msecs = tv.tv_sec - prev_tv.tv_sec;
+                       msecs <<= 10;
+                       msecs += usec_to_binarymsec(tv.tv_usec - prev_tv.tv_usec);
+
+                       if (msecs > 500) {
+                               prev_tv = tv;
+                               last = total;
+
+                               /* Update averages ..*/
+                               avg_bytes += diff;
+                               avg_time += msecs;
+                               avg_bytes -= download[idx].bytes;
+                               avg_time -= download[idx].time;
+                               download[idx].bytes = diff;
+                               download[idx].time = msecs;
+                               idx++;
+                               if (idx >= NR_AVERAGE)
+                                       idx = 0;
+
+                               fprintf(stderr, "%4lu.%03luMB  (%lu kB/s)      \r",
+                                       total >> 20,
+                                       1000*((total >> 10) & 1023)>>10,
+                                       avg_bytes / avg_time );
+                       }
+               }
+       }
+       close(ofd);
+       return finish_pack(tmpfile, me);
+}
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644 (file)
index 0000000..8daa93d
--- /dev/null
@@ -0,0 +1,512 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+
+static int keep_pack;
+static int quiet;
+static int verbose;
+static int fetch_all;
+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";
+
+#define COMPLETE       (1U << 0)
+#define COMMON         (1U << 1)
+#define COMMON_REF     (1U << 2)
+#define SEEN           (1U << 3)
+#define POPPED         (1U << 4)
+
+static struct commit_list *rev_list = NULL;
+static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+       if (!(commit->object.flags & mark)) {
+               commit->object.flags |= mark;
+
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+
+               insert_by_date(commit, &rev_list);
+
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs++;
+       }
+}
+
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+       if (o && o->type == commit_type)
+               rev_list_push((struct commit *)o, SEEN);
+
+       return 0;
+}
+
+/*
+   This function marks a rev and its ancestors as common.
+   In some cases, it is desirable to mark only the ancestors (for example
+   when only the server does not yet know that they are common).
+*/
+
+static void mark_common(struct commit *commit,
+               int ancestors_only, int dont_parse)
+{
+       if (commit != NULL && !(commit->object.flags & COMMON)) {
+               struct object *o = (struct object *)commit;
+
+               if (!ancestors_only)
+                       o->flags |= COMMON;
+
+               if (!(o->flags & SEEN))
+                       rev_list_push(commit, SEEN);
+               else {
+                       struct commit_list *parents;
+
+                       if (!ancestors_only && !(o->flags & POPPED))
+                               non_common_revs--;
+                       if (!o->parsed && !dont_parse)
+                               parse_commit(commit);
+
+                       for (parents = commit->parents;
+                                       parents;
+                                       parents = parents->next)
+                               mark_common(parents->item, 0, dont_parse);
+               }
+       }
+}
+
+/*
+  Get the next rev to send, ignoring the common.
+*/
+
+static const unsigned char* get_rev(void)
+{
+       struct commit *commit = NULL;
+
+       while (commit == NULL) {
+               unsigned int mark;
+               struct commit_list* parents;
+
+               if (rev_list == NULL || non_common_revs == 0)
+                       return NULL;
+
+               commit = rev_list->item;
+               if (!(commit->object.parsed))
+                       parse_commit(commit);
+               commit->object.flags |= POPPED;
+               if (!(commit->object.flags & COMMON))
+                       non_common_revs--;
+       
+               parents = commit->parents;
+
+               if (commit->object.flags & COMMON) {
+                       /* do not send "have", and ignore ancestors */
+                       commit = NULL;
+                       mark = COMMON | SEEN;
+               } else if (commit->object.flags & COMMON_REF)
+                       /* send "have", and ignore ancestors */
+                       mark = COMMON | SEEN;
+               else
+                       /* send "have", also for its ancestors */
+                       mark = SEEN;
+
+               while (parents) {
+                       if (!(parents->item->object.flags & SEEN))
+                               rev_list_push(parents->item, mark);
+                       if (mark & COMMON)
+                               mark_common(parents->item, 1, 0);
+                       parents = parents->next;
+               }
+
+               rev_list = rev_list->next;
+       }
+
+       return commit->object.sha1;
+}
+
+static int find_common(int fd[2], unsigned char *result_sha1,
+                      struct ref *refs)
+{
+       int fetching;
+       int count = 0, flushes = 0, retval;
+       const unsigned char *sha1;
+
+       for_each_ref(rev_list_insert_ref);
+
+       fetching = 0;
+       for ( ; refs ; refs = refs->next) {
+               unsigned char *remote = refs->old_sha1;
+               struct object *o;
+
+               /*
+                * If that object is complete (i.e. it is an ancestor of a
+                * local ref), we tell them we have it but do not have to
+                * tell them about its ancestors, which they already know
+                * about.
+                *
+                * We use lookup_object here because we are only
+                * interested in the case we *know* the object is
+                * reachable and we have already scanned it.
+                */
+               if (((o = lookup_object(remote)) != NULL) &&
+                               (o->flags & COMPLETE)) {
+                       continue;
+               }
+
+               packet_write(fd[1], "want %s%s%s\n", sha1_to_hex(remote),
+                            (multi_ack ? " multi_ack" : ""),
+                            (use_thin_pack ? " thin-pack" : ""));
+               fetching++;
+       }
+       packet_flush(fd[1]);
+       if (!fetching)
+               return 1;
+
+       flushes = 0;
+       retval = -1;
+       while ((sha1 = get_rev())) {
+               packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+               if (verbose)
+                       fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+               if (!(31 & ++count)) {
+                       int ack;
+
+                       packet_flush(fd[1]);
+                       flushes++;
+
+                       /*
+                        * We keep one window "ahead" of the other side, and
+                        * will wait for an ACK only on the next one
+                        */
+                       if (count == 32)
+                               continue;
+
+                       do {
+                               ack = get_ack(fd[0], result_sha1);
+                               if (verbose && ack)
+                                       fprintf(stderr, "got ack %d %s\n", ack,
+                                                       sha1_to_hex(result_sha1));
+                               if (ack == 1) {
+                                       flushes = 0;
+                                       multi_ack = 0;
+                                       retval = 0;
+                                       goto done;
+                               } else if (ack == 2) {
+                                       struct commit *commit =
+                                               lookup_commit(result_sha1);
+                                       mark_common(commit, 0, 1);
+                                       retval = 0;
+                               }
+                       } while (ack);
+                       flushes--;
+               }
+       }
+done:
+       packet_write(fd[1], "done\n");
+       if (verbose)
+               fprintf(stderr, "done\n");
+       if (retval != 0) {
+               multi_ack = 0;
+               flushes++;
+       }
+       while (flushes || multi_ack) {
+               int ack = get_ack(fd[0], result_sha1);
+               if (ack) {
+                       if (verbose)
+                               fprintf(stderr, "got ack (%d) %s\n", ack,
+                                       sha1_to_hex(result_sha1));
+                       if (ack == 1)
+                               return 0;
+                       multi_ack = 1;
+                       continue;
+               }
+               flushes--;
+       }
+       return retval;
+}
+
+static struct commit_list *complete = NULL;
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+
+       while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o->flags |= COMPLETE;
+               o = parse_object(t->tagged->sha1);
+       }
+       if (o && o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+               commit->object.flags |= COMPLETE;
+               insert_by_date(commit, &complete);
+       }
+       return 0;
+}
+
+static void mark_recent_complete_commits(unsigned long cutoff)
+{
+       while (complete && cutoff <= complete->item->date) {
+               if (verbose)
+                       fprintf(stderr, "Marking %s as complete\n",
+                               sha1_to_hex(complete->item->object.sha1));
+               pop_most_recent_commit(&complete, COMPLETE);
+       }
+}
+
+static void filter_refs(struct ref **refs, int nr_match, char **match)
+{
+       struct ref **return_refs;
+       struct ref *newlist = NULL;
+       struct ref **newtail = &newlist;
+       struct ref *ref, *next;
+       struct ref *fastarray[32];
+
+       if (nr_match && !fetch_all) {
+               if (ARRAY_SIZE(fastarray) < nr_match)
+                       return_refs = xcalloc(nr_match, sizeof(struct ref *));
+               else {
+                       return_refs = fastarray;
+                       memset(return_refs, 0, sizeof(struct ref *) * nr_match);
+               }
+       }
+       else
+               return_refs = NULL;
+
+       for (ref = *refs; ref; ref = next) {
+               next = ref->next;
+               if (!memcmp(ref->name, "refs/", 5) &&
+                   check_ref_format(ref->name + 5))
+                       ; /* trash */
+               else if (fetch_all) {
+                       *newtail = ref;
+                       ref->next = NULL;
+                       newtail = &ref->next;
+                       continue;
+               }
+               else {
+                       int order = path_match(ref->name, nr_match, match);
+                       if (order) {
+                               return_refs[order-1] = ref;
+                               continue; /* we will link it later */
+                       }
+               }
+               free(ref);
+       }
+
+       if (!fetch_all) {
+               int i;
+               for (i = 0; i < nr_match; i++) {
+                       ref = return_refs[i];
+                       if (ref) {
+                               *newtail = ref;
+                               ref->next = NULL;
+                               newtail = &ref->next;
+                       }
+               }
+               if (return_refs != fastarray)
+                       free(return_refs);
+       }
+       *refs = newlist;
+}
+
+static int everything_local(struct ref **refs, int nr_match, char **match)
+{
+       struct ref *ref;
+       int retval;
+       unsigned long cutoff = 0;
+
+       track_object_refs = 0;
+       save_commit_buffer = 0;
+
+       for (ref = *refs; ref; ref = ref->next) {
+               struct object *o;
+
+               o = parse_object(ref->old_sha1);
+               if (!o)
+                       continue;
+
+               /* We already have it -- which may mean that we were
+                * in sync with the other side at some time after
+                * that (it is OK if we guess wrong here).
+                */
+               if (o->type == commit_type) {
+                       struct commit *commit = (struct commit *)o;
+                       if (!cutoff || cutoff < commit->date)
+                               cutoff = commit->date;
+               }
+       }
+
+       for_each_ref(mark_complete);
+       if (cutoff)
+               mark_recent_complete_commits(cutoff);
+
+       /*
+        * Mark all complete remote refs as common refs.
+        * Don't mark them common yet; the server has to be told so first.
+        */
+       for (ref = *refs; ref; ref = ref->next) {
+               struct object *o = deref_tag(lookup_object(ref->old_sha1),
+                                            NULL, 0);
+
+               if (!o || o->type != commit_type || !(o->flags & COMPLETE))
+                       continue;
+
+               if (!(o->flags & SEEN)) {
+                       rev_list_push((struct commit *)o, COMMON_REF | SEEN);
+
+                       mark_common((struct commit *)o, 1, 1);
+               }
+       }
+
+       filter_refs(refs, nr_match, match);
+
+       for (retval = 1, ref = *refs; ref ; ref = ref->next) {
+               const unsigned char *remote = ref->old_sha1;
+               unsigned char local[20];
+               struct object *o;
+
+               o = lookup_object(remote);
+               if (!o || !(o->flags & COMPLETE)) {
+                       retval = 0;
+                       if (!verbose)
+                               continue;
+                       fprintf(stderr,
+                               "want %s (%s)\n", sha1_to_hex(remote),
+                               ref->name);
+                       continue;
+               }
+
+               memcpy(ref->new_sha1, local, 20);
+               if (!verbose)
+                       continue;
+               fprintf(stderr,
+                       "already have %s (%s)\n", sha1_to_hex(remote),
+                       ref->name);
+       }
+       return retval;
+}
+
+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 (server_supports("multi_ack")) {
+               if (verbose)
+                       fprintf(stderr, "Server supports multi_ack\n");
+               multi_ack = 1;
+       }
+       if (!ref) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
+       }
+       if (everything_local(&ref, nr_match, match)) {
+               packet_flush(fd[1]);
+               goto all_done;
+       }
+       if (find_common(fd, sha1, ref) < 0)
+               if (!keep_pack)
+                       /* When cloning, it is not unusual to have
+                        * no common commit.
+                        */
+                       fprintf(stderr, "warning: no common commits\n");
+
+       if (keep_pack)
+               status = receive_keep_pack(fd, "git-fetch-pack", quiet);
+       else
+               status = receive_unpack_pack(fd, "git-fetch-pack", quiet);
+
+       if (status)
+               die("git-fetch-pack: fetch failed.");
+
+ all_done:
+       while (ref) {
+               printf("%s %s\n",
+                      sha1_to_hex(ref->old_sha1), ref->name);
+               ref = ref->next;
+       }
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret, nr_heads;
+       char *dest = NULL, **heads;
+       int fd[2];
+       pid_t pid;
+
+       setup_git_directory();
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strncmp("--exec=", arg, 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
+                               quiet = 1;
+                               continue;
+                       }
+                       if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
+                               keep_pack = 1;
+                               continue;
+                       }
+                       if (!strcmp("--thin", arg)) {
+                               use_thin_pack = 1;
+                               continue;
+                       }
+                       if (!strcmp("--all", arg)) {
+                               fetch_all = 1;
+                               continue;
+                       }
+                       if (!strcmp("-v", arg)) {
+                               verbose = 1;
+                               continue;
+                       }
+                       usage(fetch_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(fetch_pack_usage);
+       if (keep_pack)
+               use_thin_pack = 0;
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = fetch_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+
+       if (!ret && nr_heads) {
+               /* If the heads to pull were given, we should have
+                * consumed all of them by matching the remote.
+                * Otherwise, 'git-fetch remote no-such-ref' would
+                * silently succeed without issuing an error.
+                */
+               for (i = 0; i < nr_heads; i++)
+                       if (heads[i] && heads[i][0]) {
+                               error("no such remote ref %s", heads[i]);
+                               ret = 1;
+                       }
+       }
+
+       return ret;
+}
diff --git a/fetch.c b/fetch.c
new file mode 100644 (file)
index 0000000..cf6c994
--- /dev/null
+++ b/fetch.c
@@ -0,0 +1,268 @@
+#include "fetch.h"
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "tag.h"
+#include "blob.h"
+#include "refs.h"
+
+const char *write_ref = NULL;
+const char *write_ref_log_details = NULL;
+
+int get_tree = 0;
+int get_history = 0;
+int get_all = 0;
+int get_verbosely = 0;
+int get_recover = 0;
+static unsigned char current_commit_sha1[20];
+
+void pull_say(const char *fmt, const char *hex) 
+{
+       if (get_verbosely)
+               fprintf(stderr, fmt, hex);
+}
+
+static void report_missing(const char *what, const unsigned char *missing)
+{
+       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));
+}
+
+static int process(struct object *obj);
+
+static int process_tree(struct tree *tree)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       if (parse_tree(tree))
+               return -1;
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+       while (tree_entry(&desc, &entry)) {
+               struct object *obj = NULL;
+
+               if (S_ISDIR(entry.mode)) {
+                       struct tree *tree = lookup_tree(entry.sha1);
+                       if (tree)
+                               obj = &tree->object;
+               }
+               else {
+                       struct blob *blob = lookup_blob(entry.sha1);
+                       if (blob)
+                               obj = &blob->object;
+               }
+               if (!obj || process(obj))
+                       return -1;
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+       tree->size = 0;
+       return 0;
+}
+
+#define COMPLETE       (1U << 0)
+#define SEEN           (1U << 1)
+#define TO_SCAN                (1U << 2)
+
+static struct commit_list *complete = NULL;
+
+static int process_commit(struct commit *commit)
+{
+       if (parse_commit(commit))
+               return -1;
+
+       while (complete && complete->item->date >= commit->date) {
+               pop_most_recent_commit(&complete, COMPLETE);
+       }
+
+       if (commit->object.flags & COMPLETE)
+               return 0;
+
+       memcpy(current_commit_sha1, commit->object.sha1, 20);
+
+       pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
+
+       if (get_tree) {
+               if (process(&commit->tree->object))
+                       return -1;
+               if (!get_all)
+                       get_tree = 0;
+       }
+       if (get_history) {
+               struct commit_list *parents = commit->parents;
+               for (; parents; parents = parents->next) {
+                       if (process(&parents->item->object))
+                               return -1;
+               }
+       }
+       return 0;
+}
+
+static int process_tag(struct tag *tag)
+{
+       if (parse_tag(tag))
+               return -1;
+       return process(tag->tagged);
+}
+
+static struct object_list *process_queue = NULL;
+static struct object_list **process_queue_end = &process_queue;
+
+static int process_object(struct object *obj)
+{
+       if (obj->type == commit_type) {
+               if (process_commit((struct commit *)obj))
+                       return -1;
+               return 0;
+       }
+       if (obj->type == tree_type) {
+               if (process_tree((struct tree *)obj))
+                       return -1;
+               return 0;
+       }
+       if (obj->type == blob_type) {
+               return 0;
+       }
+       if (obj->type == tag_type) {
+               if (process_tag((struct tag *)obj))
+                       return -1;
+               return 0;
+       }
+       return error("Unable to determine requirements "
+                    "of type %s for %s",
+                    obj->type, sha1_to_hex(obj->sha1));
+}
+
+static int process(struct object *obj)
+{
+       if (obj->flags & SEEN)
+               return 0;
+       obj->flags |= SEEN;
+
+       if (has_sha1_file(obj->sha1)) {
+               /* We already have it, so we should scan it now. */
+               obj->flags |= TO_SCAN;
+       }
+       else {
+               if (obj->flags & COMPLETE)
+                       return 0;
+               prefetch(obj->sha1);
+       }
+               
+       object_list_insert(obj, process_queue_end);
+       process_queue_end = &(*process_queue_end)->next;
+       return 0;
+}
+
+static int loop(void)
+{
+       struct object_list *elem;
+
+       while (process_queue) {
+               struct object *obj = process_queue->item;
+               elem = process_queue;
+               process_queue = elem->next;
+               free(elem);
+               if (!process_queue)
+                       process_queue_end = &process_queue;
+
+               /* If we are not scanning this object, we placed it in
+                * the queue because we needed to fetch it first.
+                */
+               if (! (obj->flags & TO_SCAN)) {
+                       if (fetch(obj->sha1)) {
+                               report_missing(obj->type
+                                              ? obj->type
+                                              : "object", obj->sha1);
+                               return -1;
+                       }
+               }
+               if (!obj->type)
+                       parse_object(obj->sha1);
+               if (process_object(obj))
+                       return -1;
+       }
+       return 0;
+}
+
+static int interpret_target(char *target, unsigned char *sha1)
+{
+       if (!get_sha1_hex(target, sha1))
+               return 0;
+       if (!check_ref_format(target)) {
+               if (!fetch_ref(target, sha1)) {
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       if (commit) {
+               commit->object.flags |= COMPLETE;
+               insert_by_date(commit, &complete);
+       }
+       return 0;
+}
+
+int pull(char *target)
+{
+       struct ref_lock *lock = NULL;
+       unsigned char sha1[20];
+       char *msg;
+       int ret;
+
+       save_commit_buffer = 0;
+       track_object_refs = 0;
+       if (write_ref) {
+               lock = lock_ref_sha1(write_ref, NULL, 0);
+               if (!lock) {
+                       error("Can't lock ref %s", write_ref);
+                       return -1;
+               }
+       }
+
+       if (!get_recover)
+               for_each_ref(mark_complete);
+
+       if (interpret_target(target, sha1)) {
+               error("Could not interpret %s as something to pull", target);
+               if (lock)
+                       unlock_ref(lock);
+               return -1;
+       }
+       if (process(lookup_unknown_object(sha1))) {
+               if (lock)
+                       unlock_ref(lock);
+               return -1;
+       }
+       if (loop()) {
+               if (lock)
+                       unlock_ref(lock);
+               return -1;
+       }
+
+       if (write_ref) {
+               if (write_ref_log_details) {
+                       msg = xmalloc(strlen(write_ref_log_details) + 12);
+                       sprintf(msg, "fetch from %s", write_ref_log_details);
+               }
+               else
+                       msg = NULL;
+               ret = write_ref_sha1(lock, sha1, msg ? msg : "fetch (unknown)");
+               if (msg)
+                       free(msg);
+               return ret;
+       }
+       return 0;
+}
diff --git a/fetch.h b/fetch.h
new file mode 100644 (file)
index 0000000..841bb1a
--- /dev/null
+++ b/fetch.h
@@ -0,0 +1,51 @@
+#ifndef PULL_H
+#define PULL_H
+
+/*
+ * Fetch object given SHA1 from the remote, and store it locally under
+ * GIT_OBJECT_DIRECTORY.  Return 0 on success, -1 on failure.  To be
+ * provided by the particular implementation.
+ */
+extern int fetch(unsigned char *sha1);
+
+/*
+ * Fetch the specified object and store it locally; fetch() will be
+ * called later to determine success. To be provided by the particular
+ * implementation.
+ */
+extern void prefetch(unsigned char *sha1);
+
+/*
+ * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store
+ * the 20-byte SHA1 in sha1.  Return 0 on success, -1 on failure.  To
+ * be provided by the particular implementation.
+ */
+extern int fetch_ref(char *ref, unsigned char *sha1);
+
+/* If set, the ref filename to write the target value to. */
+extern const char *write_ref;
+
+/* If set additional text will appear in the ref log. */
+extern const char *write_ref_log_details;
+
+/* Set to fetch the target tree. */
+extern int get_tree;
+
+/* Set to fetch the commit history. */
+extern int get_history;
+
+/* Set to fetch the trees in the commit history. */
+extern int get_all;
+
+/* Set to be verbose */
+extern int get_verbosely;
+
+/* Set to check on all reachable objects. */
+extern int get_recover;
+
+/* Report what we got under get_verbosely */
+extern void pull_say(const char *, const char *);
+
+extern int pull(char *target);
+
+#endif /* PULL_H */
diff --git a/fsck-objects.c b/fsck-objects.c
new file mode 100644 (file)
index 0000000..33ce366
--- /dev/null
@@ -0,0 +1,597 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+#include "refs.h"
+#include "pack.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+
+#define REACHABLE 0x0001
+#define SEEN      0x0002
+
+static int show_root = 0;
+static int show_tags = 0;
+static int show_unreachable = 0;
+static int check_full = 0;
+static int check_strict = 0;
+static int keep_cache_objects = 0;
+static unsigned char head_sha1[20];
+
+#ifdef NO_D_INO_IN_DIRENT
+#define SORT_DIRENT 0
+#define DIRENT_SORT_HINT(de) 0
+#else
+#define SORT_DIRENT 1
+#define DIRENT_SORT_HINT(de) ((de)->d_ino)
+#endif
+
+static void objreport(struct object *obj, const char *severity,
+                      const char *err, va_list params)
+{
+       fprintf(stderr, "%s in %s %s: ",
+               severity, obj->type, sha1_to_hex(obj->sha1));
+       vfprintf(stderr, err, params);
+       fputs("\n", stderr);
+}
+
+static int objerror(struct object *obj, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       objreport(obj, "error", err, params);
+       va_end(params);
+       return -1;
+}
+
+static int objwarning(struct object *obj, const char *err, ...)
+{
+       va_list params;
+       va_start(params, err);
+       objreport(obj, "warning", err, params);
+       va_end(params);
+       return -1;
+}
+
+
+static void check_connectivity(void)
+{
+       int i;
+
+       /* Look up all the requirements, warn about missing objects.. */
+       for (i = 0; i < obj_allocs; i++) {
+               struct object *obj = objs[i];
+
+               if (!obj)
+                       continue;
+
+               if (!obj->parsed) {
+                       if (has_sha1_file(obj->sha1))
+                               ; /* it is in pack */
+                       else
+                               printf("missing %s %s\n",
+                                      obj->type, sha1_to_hex(obj->sha1));
+                       continue;
+               }
+
+               if (obj->refs) {
+                       const struct object_refs *refs = obj->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",
+                                      obj->type, sha1_to_hex(obj->sha1));
+                               printf("              to %7s %s\n",
+                                      ref->type, sha1_to_hex(ref->sha1));
+                       }
+               }
+
+               if (show_unreachable && !(obj->flags & REACHABLE)) {
+                       printf("unreachable %s %s\n",
+                              obj->type, sha1_to_hex(obj->sha1));
+                       continue;
+               }
+
+               if (!obj->used) {
+                       printf("dangling %s %s\n", obj->type, 
+                              sha1_to_hex(obj->sha1));
+               }
+       }
+}
+
+/*
+ * The entries in a tree are ordered in the _path_ order,
+ * which means that a directory entry is ordered by adding
+ * a slash to the end of it.
+ *
+ * So a directory called "a" is ordered _after_ a file
+ * called "a.c", because "a/" sorts after "a.c".
+ */
+#define TREE_UNORDERED (-1)
+#define TREE_HAS_DUPS  (-2)
+
+static int verify_ordered(unsigned mode1, const char *name1, unsigned mode2, const char *name2)
+{
+       int len1 = strlen(name1);
+       int len2 = strlen(name2);
+       int len = len1 < len2 ? len1 : len2;
+       unsigned char c1, c2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp < 0)
+               return 0;
+       if (cmp > 0)
+               return TREE_UNORDERED;
+
+       /*
+        * Ok, the first <len> characters are the same.
+        * Now we need to order the next one, but turn
+        * a '\0' into a '/' for a directory entry.
+        */
+       c1 = name1[len];
+       c2 = name2[len];
+       if (!c1 && !c2)
+               /*
+                * git-write-tree used to write out a nonsense tree that has
+                * entries with the same name, one blob and one tree.  Make
+                * sure we do not have duplicate entries.
+                */
+               return TREE_HAS_DUPS;
+       if (!c1 && S_ISDIR(mode1))
+               c1 = '/';
+       if (!c2 && S_ISDIR(mode2))
+               c2 = '/';
+       return c1 < c2 ? 0 : TREE_UNORDERED;
+}
+
+static int fsck_tree(struct tree *item)
+{
+       int retval;
+       int has_full_path = 0;
+       int has_zero_pad = 0;
+       int has_bad_modes = 0;
+       int has_dup_entries = 0;
+       int not_properly_sorted = 0;
+       struct tree_desc desc;
+       unsigned o_mode;
+       const char *o_name;
+       const unsigned char *o_sha1;
+
+       desc.buf = item->buffer;
+       desc.size = item->size;
+
+       o_mode = 0;
+       o_name = NULL;
+       o_sha1 = NULL;
+       while (desc.size) {
+               unsigned mode;
+               const char *name;
+               const unsigned char *sha1;
+
+               sha1 = tree_entry_extract(&desc, &name, &mode);
+
+               if (strchr(name, '/'))
+                       has_full_path = 1;
+               has_zero_pad |= *(char *)desc.buf == '0';
+               update_tree_entry(&desc);
+
+               switch (mode) {
+               /*
+                * Standard modes..
+                */
+               case S_IFREG | 0755:
+               case S_IFREG | 0644:
+               case S_IFLNK:
+               case S_IFDIR:
+                       break;
+               /*
+                * This is nonstandard, but we had a few of these
+                * early on when we honored the full set of mode
+                * bits..
+                */
+               case S_IFREG | 0664:
+                       if (!check_strict)
+                               break;
+               default:
+                       has_bad_modes = 1;
+               }
+
+               if (o_name) {
+                       switch (verify_ordered(o_mode, o_name, mode, name)) {
+                       case TREE_UNORDERED:
+                               not_properly_sorted = 1;
+                               break;
+                       case TREE_HAS_DUPS:
+                               has_dup_entries = 1;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               o_mode = mode;
+               o_name = name;
+               o_sha1 = sha1;
+       }
+       free(item->buffer);
+       item->buffer = NULL;
+
+       retval = 0;
+       if (has_full_path) {
+               objwarning(&item->object, "contains full pathnames");
+       }
+       if (has_zero_pad) {
+               objwarning(&item->object, "contains zero-padded file modes");
+       }
+       if (has_bad_modes) {
+               objwarning(&item->object, "contains bad file modes");
+       }
+       if (has_dup_entries) {
+               retval = objerror(&item->object, "contains duplicate file entries");
+       }
+       if (not_properly_sorted) {
+               retval = objerror(&item->object, "not properly sorted");
+       }
+       return retval;
+}
+
+static int fsck_commit(struct commit *commit)
+{
+       char *buffer = commit->buffer;
+       unsigned char tree_sha1[20], sha1[20];
+
+       if (memcmp(buffer, "tree ", 5))
+               return objerror(&commit->object, "invalid format - expected 'tree' line");
+       if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
+               return objerror(&commit->object, "invalid 'tree' line format - bad sha1");
+       buffer += 46;
+       while (!memcmp(buffer, "parent ", 7)) {
+               if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
+                       return objerror(&commit->object, "invalid 'parent' line format - bad sha1");
+               buffer += 48;
+       }
+       if (memcmp(buffer, "author ", 7))
+               return objerror(&commit->object, "invalid format - expected 'author' line");
+       free(commit->buffer);
+       commit->buffer = NULL;
+       if (!commit->tree)
+               return objerror(&commit->object, "could not load commit's tree %s", tree_sha1);
+       if (!commit->parents && show_root)
+               printf("root %s\n", sha1_to_hex(commit->object.sha1));
+       if (!commit->date)
+               printf("bad commit date in %s\n", 
+                      sha1_to_hex(commit->object.sha1));
+       return 0;
+}
+
+static int fsck_tag(struct tag *tag)
+{
+       struct object *tagged = tag->tagged;
+
+       if (!tagged) {
+               return objerror(&tag->object, "could not load tagged object");
+       }
+       if (!show_tags)
+               return 0;
+
+       printf("tagged %s %s", tagged->type, sha1_to_hex(tagged->sha1));
+       printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+       return 0;
+}
+
+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));
+       if (obj->flags & SEEN)
+               return 0;
+       obj->flags |= SEEN;
+       if (obj->type == blob_type)
+               return 0;
+       if (obj->type == tree_type)
+               return fsck_tree((struct tree *) obj);
+       if (obj->type == commit_type)
+               return fsck_commit((struct commit *) obj);
+       if (obj->type == tag_type)
+               return fsck_tag((struct tag *) obj);
+       /* By now, parse_object() would've returned NULL instead. */
+       return objerror(obj, "unknown type '%s' (internal fsck error)", obj->type);
+}
+
+/*
+ * This is the sorting chunk size: make it reasonably
+ * big so that we can sort well..
+ */
+#define MAX_SHA1_ENTRIES (1024)
+
+struct sha1_entry {
+       unsigned long ino;
+       unsigned char sha1[20];
+};
+
+static struct {
+       unsigned long nr;
+       struct sha1_entry *entry[MAX_SHA1_ENTRIES];
+} sha1_list;
+
+static int ino_compare(const void *_a, const void *_b)
+{
+       const struct sha1_entry *a = _a, *b = _b;
+       unsigned long ino1 = a->ino, ino2 = b->ino;
+       return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
+}
+
+static void fsck_sha1_list(void)
+{
+       int i, nr = sha1_list.nr;
+
+       if (SORT_DIRENT)
+               qsort(sha1_list.entry, nr,
+                     sizeof(struct sha1_entry *), ino_compare);
+       for (i = 0; i < nr; i++) {
+               struct sha1_entry *entry = sha1_list.entry[i];
+               unsigned char *sha1 = entry->sha1;
+
+               sha1_list.entry[i] = NULL;
+               fsck_sha1(sha1);
+               free(entry);
+       }
+       sha1_list.nr = 0;
+}
+
+static void add_sha1_list(unsigned char *sha1, unsigned long ino)
+{
+       struct sha1_entry *entry = xmalloc(sizeof(*entry));
+       int nr;
+
+       entry->ino = ino;
+       memcpy(entry->sha1, sha1, 20);
+       nr = sha1_list.nr;
+       if (nr == MAX_SHA1_ENTRIES) {
+               fsck_sha1_list();
+               nr = 0;
+       }
+       sha1_list.entry[nr] = entry;
+       sha1_list.nr = ++nr;
+}
+
+static int fsck_dir(int i, char *path)
+{
+       DIR *dir = opendir(path);
+       struct dirent *de;
+
+       if (!dir)
+               return 0;
+
+       while ((de = readdir(dir)) != NULL) {
+               char name[100];
+               unsigned char sha1[20];
+               int len = strlen(de->d_name);
+
+               switch (len) {
+               case 2:
+                       if (de->d_name[1] != '.')
+                               break;
+               case 1:
+                       if (de->d_name[0] != '.')
+                               break;
+                       continue;
+               case 38:
+                       sprintf(name, "%02x", i);
+                       memcpy(name+2, de->d_name, len+1);
+                       if (get_sha1_hex(name, sha1) < 0)
+                               break;
+                       add_sha1_list(sha1, DIRENT_SORT_HINT(de));
+                       continue;
+               }
+               fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+       }
+       closedir(dir);
+       return 0;
+}
+
+static int default_refs = 0;
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+{
+       struct object *obj;
+
+       obj = lookup_object(sha1);
+       if (!obj) {
+               if (has_sha1_file(sha1)) {
+                       default_refs++;
+                       return 0; /* it is in a pack */
+               }
+               error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+               /* We'll continue with the rest despite the error.. */
+               return 0;
+       }
+       default_refs++;
+       obj->used = 1;
+       mark_reachable(obj, REACHABLE);
+       return 0;
+}
+
+static void get_default_heads(void)
+{
+       for_each_ref(fsck_handle_ref);
+       if (!default_refs)
+               die("No default references");
+}
+
+static void fsck_object_dir(const char *path)
+{
+       int i;
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               fsck_dir(i, dir);
+       }
+       fsck_sha1_list();
+}
+
+static int fsck_head_link(void)
+{
+       unsigned char sha1[20];
+       const char *git_HEAD = strdup(git_path("HEAD"));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+       int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+       if (!git_refs_heads_master)
+               return error("HEAD is not a symbolic ref");
+       if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+               return error("HEAD points to something strange (%s)",
+                            git_refs_heads_master + pfxlen);
+       if (!memcmp(null_sha1, sha1, 20))
+               return error("HEAD: not a valid git pointer");
+       return 0;
+}
+
+static int fsck_cache_tree(struct cache_tree *it)
+{
+       int i;
+       int err = 0;
+
+       if (0 <= it->entry_count) {
+               struct object *obj = parse_object(it->sha1);
+               if (!obj) {
+                       error("%s: invalid sha1 pointer in cache-tree",
+                             sha1_to_hex(it->sha1));
+                       return 1;
+               }
+               mark_reachable(obj, REACHABLE);
+               obj->used = 1;
+               if (obj->type != tree_type)
+                       err |= objerror(obj, "non-tree in cache-tree");
+       }
+       for (i = 0; i < it->subtree_nr; i++)
+               err |= fsck_cache_tree(it->down[i]->cache_tree);
+       return err;
+}
+
+int main(int argc, char **argv)
+{
+       int i, heads;
+
+       track_object_refs = 1;
+       setup_git_directory();
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (!strcmp(arg, "--unreachable")) {
+                       show_unreachable = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       show_tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--root")) {
+                       show_root = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--cache")) {
+                       keep_cache_objects = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--full")) {
+                       check_full = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--strict")) {
+                       check_strict = 1;
+                       continue;
+               }
+               if (*arg == '-')
+                       usage("git-fsck-objects [--tags] [--root] [[--unreachable] [--cache] [--full] [--strict] <head-sha1>*]");
+       }
+
+       fsck_head_link();
+       fsck_object_dir(get_object_directory());
+       if (check_full) {
+               struct alternate_object_database *alt;
+               struct packed_git *p;
+               prepare_alt_odb();
+               for (alt = alt_odb_list; alt; alt = alt->next) {
+                       char namebuf[PATH_MAX];
+                       int namelen = alt->name - alt->base;
+                       memcpy(namebuf, alt->base, namelen);
+                       namebuf[namelen - 1] = 0;
+                       fsck_object_dir(namebuf);
+               }
+               prepare_packed_git();
+               for (p = packed_git; p; p = p->next)
+                       /* verify gives error messages itself */
+                       verify_pack(p, 0);
+
+               for (p = packed_git; p; p = p->next) {
+                       int num = num_packed_objects(p);
+                       for (i = 0; i < num; i++) {
+                               unsigned char sha1[20];
+                               nth_packed_object_sha1(p, i, sha1);
+                               fsck_sha1(sha1);
+                       }
+               }
+       }
+
+       heads = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i]; 
+
+               if (*arg == '-')
+                       continue;
+
+               if (!get_sha1(arg, head_sha1)) {
+                       struct object *obj = lookup_object(head_sha1);
+
+                       /* Error is printed by lookup_object(). */
+                       if (!obj)
+                               continue;
+
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+                       heads++;
+                       continue;
+               }
+               error("invalid parameter: expected sha1, got '%s'", arg);
+       }
+
+       /*
+        * If we've not been given any explicit head information, do the
+        * default ones from .git/refs. We also consider the index file
+        * in this case (ie this implies --cache).
+        */
+       if (!heads) {
+               get_default_heads();
+               keep_cache_objects = 1;
+       }
+
+       if (keep_cache_objects) {
+               int i;
+               read_cache();
+               for (i = 0; i < active_nr; i++) {
+                       struct blob *blob = lookup_blob(active_cache[i]->sha1);
+                       struct object *obj;
+                       if (!blob)
+                               continue;
+                       obj = &blob->object;
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+               }
+               if (active_cache_tree)
+                       fsck_cache_tree(active_cache_tree);
+       }
+
+       check_connectivity();
+       return 0;
+}
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
new file mode 100755 (executable)
index 0000000..ec1eda2
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+echo "/* Automatically generated by $0 */
+struct cmdname_help
+{
+    char name[16];
+    char help[64];
+};
+
+struct cmdname_help common_cmds[] = {"
+
+sort <<\EOF |
+add
+apply
+bisect
+branch
+checkout
+cherry-pick
+clone
+commit
+diff
+fetch
+grep
+init-db
+log
+merge
+mv
+prune
+pull
+push
+rebase
+reset
+revert
+rm
+show
+show-branch
+status
+tag
+verify-tag
+EOF
+while read cmd
+do
+     sed -n '
+     /NAME/,/git-'"$cmd"'/H
+     ${
+            x
+            s/.*git-'"$cmd"' - \(.*\)/  {"'"$cmd"'", "\1"},/
+           p
+     }' "Documentation/git-$cmd.txt"
+done
+echo "};"
diff --git a/get-tar-commit-id.c b/get-tar-commit-id.c
new file mode 100644 (file)
index 0000000..4166290
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005 Rene Scharfe
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define HEADERSIZE     1024
+
+int main(int argc, char **argv)
+{
+       char buffer[HEADERSIZE];
+       ssize_t n;
+
+       n = read(0, buffer, HEADERSIZE);
+       if (n < HEADERSIZE) {
+               fprintf(stderr, "read error\n");
+               return 3;
+       }
+       if (buffer[156] != 'g')
+               return 1;
+       if (memcmp(&buffer[512], "52 comment=", 11))
+               return 1;
+       n = write(1, &buffer[523], 41);
+       if (n < 41) {
+               fprintf(stderr, "write error\n");
+               return 2;
+       }
+       return 0;
+}
diff --git a/git-am.sh b/git-am.sh
new file mode 100755 (executable)
index 0000000..4232e27
--- /dev/null
+++ b/git-am.sh
@@ -0,0 +1,427 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, 2006 Junio C Hamano
+
+USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+  [--interactive] [--whitespace=<option>] <mbox>...
+  or, when resuming [--skip | --resolved]'
+. git-sh-setup
+
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+stop_here () {
+    echo "$1" >"$dotest/next"
+    exit 1
+}
+
+stop_here_user_resolve () {
+    if [ -n "$resolvemsg" ]; then
+           echo "$resolvemsg"
+           stop_here $1
+    fi
+    cmdline=$(basename $0)
+    if test '' != "$interactive"
+    then
+        cmdline="$cmdline -i"
+    fi
+    if test '' != "$threeway"
+    then
+        cmdline="$cmdline -3"
+    fi
+    if test '.dotest' != "$dotest"
+    then
+        cmdline="$cmdline -d=$dotest"
+    fi
+    echo "When you have resolved this problem run \"$cmdline --resolved\"."
+    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
+
+    stop_here $1
+}
+
+go_next () {
+       rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+               "$dotest/patch" "$dotest/info"
+       echo "$next" >"$dotest/next"
+       this=$next
+}
+
+fall_back_3way () {
+    O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+
+    rm -fr "$dotest"/patch-merge-*
+    mkdir "$dotest/patch-merge-tmp-dir"
+
+    # First see if the patch records the index info that we can use.
+    if git-apply -z --index-info "$dotest/patch" \
+       >"$dotest/patch-merge-index-info" 2>/dev/null &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-write-tree >"$dotest/patch-merge-base+" &&
+       # index has the base tree now.
+       GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+       git-apply $binary --cached <"$dotest/patch"
+    then
+       echo Using index info to reconstruct a base tree...
+       mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+       mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+    fi
+
+    test -f "$dotest/patch-merge-index" &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+    orig_tree=$(cat "$dotest/patch-merge-base") &&
+    rm -fr "$dotest"/patch-merge-* || exit 1
+
+    echo Falling back to patching base and 3-way merge...
+
+    # 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 cancelling them,
+    # saying that we reverted all those changes.
+
+    git-merge-resolve $orig_tree -- HEAD $his_tree || {
+           if test -d "$GIT_DIR/rr-cache"
+           then
+               git-rerere
+           fi
+           echo Failed to merge in the changes.
+           exit 1
+    }
+}
+
+prec=4
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
+
+while case "$#" in 0) break;; esac
+do
+       case "$1" in
+       -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
+       dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+       -d|--d|--do|--dot|--dote|--dotes|--dotest)
+       case "$#" in 1) usage ;; esac; shift
+       dotest="$1"; shift;;
+
+       -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
+       --interacti|--interactiv|--interactive)
+       interactive=t; shift ;;
+
+       -b|--b|--bi|--bin|--bina|--binar|--binary)
+       binary=t; shift ;;
+
+       -3|--3|--3w|--3wa|--3way)
+       threeway=t; shift ;;
+       -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+       sign=t; shift ;;
+       -u|--u|--ut|--utf|--utf8)
+       utf8=t; shift ;;
+       -k|--k|--ke|--kee|--keep)
+       keep=t; shift ;;
+
+       -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
+       resolved=t; shift ;;
+
+       --sk|--ski|--skip)
+       skip=t; shift ;;
+
+       --whitespace=*)
+       ws=$1; shift ;;
+
+       --resolvemsg=*)
+       resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
+
+       --)
+       shift; break ;;
+       -*)
+       usage ;;
+       *)
+       break ;;
+       esac
+done
+
+# If the dotest directory exists, but we have finished applying all the
+# patches in them, clear it out.
+if test -d "$dotest" &&
+   last=$(cat "$dotest/last") &&
+   next=$(cat "$dotest/next") &&
+   test $# != 0 &&
+   test "$next" -gt "$last"
+then
+   rm -fr "$dotest"
+fi
+
+if test -d "$dotest"
+then
+       test ",$#," = ",0," ||
+       die "previous dotest directory $dotest still exists but mbox given."
+       resume=yes
+else
+       # Make sure we are not given --skip nor --resolved
+       test ",$skip,$resolved," = ,,, ||
+               die "Resolve operation not in progress, we are not resuming."
+
+       # Start afresh.
+       mkdir -p "$dotest" || exit
+
+       git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
+               rm -fr "$dotest"
+               exit 1
+       }
+
+       # -b, -s, -u, -k and --whitespace flags are kept for the
+       # resuming session after a patch failure.
+       # -3 and -i can and must be given when resuming.
+       echo "$binary" >"$dotest/binary"
+       echo " $ws" >"$dotest/whitespace"
+       echo "$sign" >"$dotest/sign"
+       echo "$utf8" >"$dotest/utf8"
+       echo "$keep" >"$dotest/keep"
+       echo 1 >"$dotest/next"
+fi
+
+case "$resolved" in
+'')
+       files=$(git-diff-index --cached --name-only HEAD) || exit
+       if [ "$files" ]; then
+          echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+          exit 1
+       fi
+esac
+
+if test "$(cat "$dotest/binary")" = t
+then
+       binary=--allow-binary-replacement
+fi
+if test "$(cat "$dotest/utf8")" = t
+then
+       utf8=-u
+fi
+if test "$(cat "$dotest/keep")" = t
+then
+       keep=-k
+fi
+ws=`cat "$dotest/whitespace"`
+if test "$(cat "$dotest/sign")" = t
+then
+       SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /'
+               `
+else
+       SIGNOFF=
+fi
+
+last=`cat "$dotest/last"`
+this=`cat "$dotest/next"`
+if test "$skip" = t
+then
+       this=`expr "$this" + 1`
+       resume=
+fi
+
+if test "$this" -gt "$last"
+then
+       echo Nothing to do.
+       rm -fr "$dotest"
+       exit
+fi
+
+while test "$this" -le "$last"
+do
+       msgnum=`printf "%0${prec}d" $this`
+       next=`expr "$this" + 1`
+       test -f "$dotest/$msgnum" || {
+               resume=
+               go_next
+               continue
+       }
+
+       # If we are not resuming, parse and extract the patch information
+       # into separate files:
+       #  - info records the authorship and title
+       #  - msg is the rest of commit log message
+       #  - patch is the patch body.
+       #
+       # When we are resuming, these files are either already prepared
+       # by the user, or the user can tell us to do so by --resolved flag.
+       case "$resume" in
+       '')
+               git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+                       <"$dotest/$msgnum" >"$dotest/info" ||
+                       stop_here $this
+               git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+               ;;
+       esac
+
+       GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+       GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+       GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+
+       if test -z "$GIT_AUTHOR_EMAIL"
+       then
+               echo "Patch does not have a valid e-mail address."
+               stop_here $this
+       fi
+
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+
+       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
+       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
+
+       case "$resume" in
+       '')
+           if test '' != "$SIGNOFF"
+           then
+               LAST_SIGNED_OFF_BY=`
+                   sed -ne '/^Signed-off-by: /p' \
+                   "$dotest/msg-clean" |
+                   tail -n 1
+               `
+               ADD_SIGNOFF=`
+                   test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               }`
+           else
+               ADD_SIGNOFF=
+           fi
+           {
+               echo "$SUBJECT"
+               if test -s "$dotest/msg-clean"
+               then
+                       echo
+                       cat "$dotest/msg-clean"
+               fi
+               if test '' != "$ADD_SIGNOFF"
+               then
+                       echo "$ADD_SIGNOFF"
+               fi
+           } >"$dotest/final-commit"
+           ;;
+       *)
+               case "$resolved$interactive" in
+               tt)
+                       # This is used only for interactive view option.
+                       git-diff-index -p --cached HEAD >"$dotest/patch"
+                       ;;
+               esac
+       esac
+
+       resume=
+       if test "$interactive" = t
+       then
+           test -t 0 ||
+           die "cannot be interactive without stdin connected to a terminal."
+           action=again
+           while test "$action" = again
+           do
+               echo "Commit Body is:"
+               echo "--------------------------"
+               cat "$dotest/final-commit"
+               echo "--------------------------"
+               printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+               read reply
+               case "$reply" in
+               [yY]*) action=yes ;;
+               [aA]*) action=yes interactive= ;;
+               [nN]*) action=skip ;;
+               [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+                      action=again ;;
+               [vV]*) action=again
+                      LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+               *)     action=again ;;
+               esac
+           done
+       else
+           action=yes
+       fi
+
+       if test $action = skip
+       then
+               go_next
+               continue
+       fi
+
+       if test -x "$GIT_DIR"/hooks/applypatch-msg
+       then
+               "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
+               stop_here $this
+       fi
+
+       echo
+       echo "Applying '$SUBJECT'"
+       echo
+
+       case "$resolved" in
+       '')
+               git-apply $binary --index $ws "$dotest/patch"
+               apply_status=$?
+               ;;
+       t)
+               # Resolved means the user did all the hard work, and
+               # we do not have to do any patch application.  Just
+               # trust what the user has in the index file and the
+               # working tree.
+               resolved=
+               changed="$(git-diff-index --cached --name-only HEAD)"
+               if test '' = "$changed"
+               then
+                       echo "No changes - did you forget update-index?"
+                       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?"
+                       stop_here_user_resolve $this
+               fi
+               apply_status=0
+               ;;
+       esac
+
+       if test $apply_status = 1 && test "$threeway" = t
+       then
+               if (fall_back_3way)
+               then
+                   # Applying the patch to an earlier tree and merging the
+                   # result may have produced the same tree as ours.
+                   changed="$(git-diff-index --cached --name-only HEAD)"
+                   if test '' = "$changed"
+                   then
+                           echo No changes -- Patch already applied.
+                           go_next
+                           continue
+                   fi
+                   # clear apply_status -- we have successfully merged.
+                   apply_status=0
+               fi
+       fi
+       if test $apply_status != 0
+       then
+               echo Patch failed at $msgnum.
+               stop_here_user_resolve $this
+       fi
+
+       if test -x "$GIT_DIR"/hooks/pre-applypatch
+       then
+               "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+       fi
+
+       tree=$(git-write-tree) &&
+       echo Wrote tree $tree &&
+       parent=$(git-rev-parse --verify HEAD) &&
+       commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       echo Committed: $commit &&
+       git-update-ref -m "am: $SUBJECT" HEAD $commit $parent ||
+       stop_here $this
+
+       if test -x "$GIT_DIR"/hooks/post-applypatch
+       then
+               "$GIT_DIR"/hooks/post-applypatch
+       fi
+
+       go_next
+done
+
+rm -fr "$dotest"
diff --git a/git-annotate.perl b/git-annotate.perl
new file mode 100755 (executable)
index 0000000..a6a7a48
--- /dev/null
@@ -0,0 +1,512 @@
+#!/usr/bin/perl
+# Copyright 2006, Ryan Anderson <ryan@michonline.com>
+#
+# GPL v2 (See COPYING)
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+use warnings;
+use strict;
+use Getopt::Long;
+use POSIX qw(strftime gmtime);
+use File::Basename qw(basename dirname);
+
+sub usage() {
+       print STDERR "Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
+       -l, --long
+                       Show long rev (Defaults off)
+       -t, --time
+                       Show raw timestamp (Defaults off)
+       -r, --rename
+                       Follow renames (Defaults on).
+       -S, --rev-file revs-file
+                       Use revs from revs-file instead of calling git-rev-list
+       -h, --help
+                       This message.
+";
+
+       exit(1);
+}
+
+our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
+
+my $rc = GetOptions(   "long|l" => \$longrev,
+                       "time|t" => \$rawtime,
+                       "help|h" => \$help,
+                       "rename|r" => \$rename,
+                       "rev-file|S=s" => \$rev_file);
+if (!$rc or $help or !@ARGV) {
+       usage();
+}
+
+my $filename = shift @ARGV;
+if (@ARGV) {
+       $starting_rev = shift @ARGV;
+}
+
+my @stack = (
+       {
+               'rev' => defined $starting_rev ? $starting_rev : "HEAD",
+               'filename' => $filename,
+       },
+);
+
+our @filelines = ();
+
+if (defined $starting_rev) {
+       @filelines = git_cat_file($starting_rev, $filename);
+} else {
+       open(F,"<",$filename)
+               or die "Failed to open filename: $!";
+
+       while(<F>) {
+               chomp;
+               push @filelines, $_;
+       }
+       close(F);
+
+}
+
+our %revs;
+our @revqueue;
+our $head;
+
+my $revsprocessed = 0;
+while (my $bound = pop @stack) {
+       my @revisions = git_rev_list($bound->{'rev'}, $bound->{'filename'});
+       foreach my $revinst (@revisions) {
+               my ($rev, @parents) = @$revinst;
+               $head ||= $rev;
+
+               if (!defined($rev)) {
+                       $rev = "";
+               }
+               $revs{$rev}{'filename'} = $bound->{'filename'};
+               if (scalar @parents > 0) {
+                       $revs{$rev}{'parents'} = \@parents;
+                       next;
+               }
+
+               if (!$rename) {
+                       next;
+               }
+
+               my $newbound = find_parent_renames($rev, $bound->{'filename'});
+               if ( exists $newbound->{'filename'} && $newbound->{'filename'} ne $bound->{'filename'}) {
+                       push @stack, $newbound;
+                       $revs{$rev}{'parents'} = [$newbound->{'rev'}];
+               }
+       }
+}
+push @revqueue, $head;
+init_claim( defined $starting_rev ? $head : 'dirty');
+unless (defined $starting_rev) {
+       my $diff = open_pipe("git","diff","-R", "HEAD", "--",$filename)
+               or die "Failed to call git diff to check for dirty state: $!";
+
+       _git_diff_parse($diff, $head, "dirty", (
+                               'author' => gitvar_name("GIT_AUTHOR_IDENT"),
+                               'author_date' => sprintf("%s +0000",time()),
+                               )
+                       );
+       close($diff);
+}
+handle_rev();
+
+
+my $i = 0;
+foreach my $l (@filelines) {
+       my ($output, $rev, $committer, $date);
+       if (ref $l eq 'ARRAY') {
+               ($output, $rev, $committer, $date) = @$l;
+               if (!$longrev && length($rev) > 8) {
+                       $rev = substr($rev,0,8);
+               }
+       } else {
+               $output = $l;
+               ($rev, $committer, $date) = ('unknown', 'unknown', 'unknown');
+       }
+
+       printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
+               format_date($date), ++$i, $output);
+}
+
+sub init_claim {
+       my ($rev) = @_;
+       for (my $i = 0; $i < @filelines; $i++) {
+               $filelines[$i] = [ $filelines[$i], '', '', '', 1];
+                       # line,
+                       # rev,
+                       # author,
+                       # date,
+                       # 1 <-- belongs to the original file.
+       }
+       $revs{$rev}{'lines'} = \@filelines;
+}
+
+
+sub handle_rev {
+       my $i = 0;
+       my %seen;
+       while (my $rev = shift @revqueue) {
+               next if $seen{$rev}++;
+
+               my %revinfo = git_commit_info($rev);
+
+               foreach my $p (@{$revs{$rev}{'parents'}}) {
+
+                       git_diff_parse($p, $rev, %revinfo);
+                       push @revqueue, $p;
+               }
+
+
+               if (scalar @{$revs{$rev}{parents}} == 0) {
+                       # We must be at the initial rev here, so claim everything that is left.
+                       for (my $i = 0; $i < @{$revs{$rev}{lines}}; $i++) {
+                               if (ref ${$revs{$rev}{lines}}[$i] eq '' || ${$revs{$rev}{lines}}[$i][1] eq '') {
+                                       claim_line($i, $rev, $revs{$rev}{lines}, %revinfo);
+                               }
+                       }
+               }
+       }
+}
+
+
+sub git_rev_list {
+       my ($rev, $file) = @_;
+
+       my $revlist;
+       if ($rev_file) {
+               open($revlist, '<' . $rev_file)
+                   or die "Failed to open $rev_file : $!";
+       } else {
+               $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
+                       or die "Failed to exec git-rev-list: $!";
+       }
+
+       my @revs;
+       while(my $line = <$revlist>) {
+               chomp $line;
+               my ($rev, @parents) = split /\s+/, $line;
+               push @revs, [ $rev, @parents ];
+       }
+       close($revlist);
+
+       printf("0 revs found for rev %s (%s)\n", $rev, $file) if (@revs == 0);
+       return @revs;
+}
+
+sub find_parent_renames {
+       my ($rev, $file) = @_;
+
+       my $patch = open_pipe("git-diff-tree", "-M50", "-r","--name-status", "-z","$rev")
+               or die "Failed to exec git-diff: $!";
+
+       local $/ = "\0";
+       my %bound;
+       my $junk = <$patch>;
+       while (my $change = <$patch>) {
+               chomp $change;
+               my $filename = <$patch>;
+               if (!defined $filename) {
+                       next;
+               }
+               chomp $filename;
+
+               if ($change =~ m/^[AMD]$/ ) {
+                       next;
+               } elsif ($change =~ m/^R/ ) {
+                       my $oldfilename = $filename;
+                       $filename = <$patch>;
+                       chomp $filename;
+                       if ( $file eq $filename ) {
+                               my $parent = git_find_parent($rev, $oldfilename);
+                               @bound{'rev','filename'} = ($parent, $oldfilename);
+                               last;
+                       }
+               }
+       }
+       close($patch);
+
+       return \%bound;
+}
+
+
+sub git_find_parent {
+       my ($rev, $filename) = @_;
+
+       my $revparent = open_pipe("git-rev-list","--remove-empty", "--parents","--max-count=1","$rev","--",$filename)
+               or die "Failed to open git-rev-list to find a single parent: $!";
+
+       my $parentline = <$revparent>;
+       chomp $parentline;
+       my ($revfound,$parent) = split m/\s+/, $parentline;
+
+       close($revparent);
+
+       return $parent;
+}
+
+
+# Get a diff between the current revision and a parent.
+# Record the commit information that results.
+sub git_diff_parse {
+       my ($parent, $rev, %revinfo) = @_;
+
+       my $diff = open_pipe("git-diff-tree","-M","-p",$rev,$parent,"--",
+                       $revs{$rev}{'filename'}, $revs{$parent}{'filename'})
+               or die "Failed to call git-diff for annotation: $!";
+
+       _git_diff_parse($diff, $parent, $rev, %revinfo);
+
+       close($diff);
+}
+
+sub _git_diff_parse {
+       my ($diff, $parent, $rev, %revinfo) = @_;
+
+       my ($ri, $pi) = (0,0);
+       my $slines = $revs{$rev}{'lines'};
+       my @plines;
+
+       my $gotheader = 0;
+       my ($remstart);
+       my ($hunk_start, $hunk_index);
+       while(<$diff>) {
+               chomp;
+               if (m/^@@ -(\d+),(\d+) \+(\d+),(\d+)/) {
+                       $remstart = $1;
+                       # Adjust for 0-based arrays
+                       $remstart--;
+                       # Reinit hunk tracking.
+                       $hunk_start = $remstart;
+                       $hunk_index = 0;
+                       $gotheader = 1;
+
+                       for (my $i = $ri; $i < $remstart; $i++) {
+                               $plines[$pi++] = $slines->[$i];
+                               $ri++;
+                       }
+                       next;
+               } elsif (!$gotheader) {
+                       next;
+               }
+
+               if (m/^\+(.*)$/) {
+                       my $line = $1;
+                       $plines[$pi++] = [ $line, '', '', '', 0 ];
+                       next;
+
+               } elsif (m/^-(.*)$/) {
+                       my $line = $1;
+                       if (get_line($slines, $ri) eq $line) {
+                               # Found a match, claim
+                               claim_line($ri, $rev, $slines, %revinfo);
+                       } else {
+                               die sprintf("Sync error: %d/%d\n|%s\n|%s\n%s => %s\n",
+                                               $ri, $hunk_start + $hunk_index,
+                                               $line,
+                                               get_line($slines, $ri),
+                                               $rev, $parent);
+                       }
+                       $ri++;
+
+               } elsif (m/^\\/) {
+                       ;
+                       # Skip \No newline at end of file.
+                       # But this can be internationalized, so only look
+                       # for an initial \
+
+               } else {
+                       if (substr($_,1) ne get_line($slines,$ri) ) {
+                               die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
+                                               $hunk_start + $hunk_index, $ri,
+                                               substr($_,1),
+                                               get_line($slines,$ri),
+                                               $rev, $parent);
+                       }
+                       $plines[$pi++] = $slines->[$ri++];
+               }
+               $hunk_index++;
+       }
+       for (my $i = $ri; $i < @{$slines} ; $i++) {
+               push @plines, $slines->[$ri++];
+       }
+
+       $revs{$parent}{lines} = \@plines;
+       return;
+}
+
+sub get_line {
+       my ($lines, $index) = @_;
+
+       return ref $lines->[$index] ne '' ? $lines->[$index][0] : $lines->[$index];
+}
+
+sub git_cat_file {
+       my ($rev, $filename) = @_;
+       return () unless defined $rev && defined $filename;
+
+       my $blob = git_ls_tree($rev, $filename);
+       die "Failed to find a blob for $filename in rev $rev\n" if !defined $blob;
+
+       my $catfile = open_pipe("git","cat-file", "blob", $blob)
+               or die "Failed to git-cat-file blob $blob (rev $rev, file $filename): " . $!;
+
+       my @lines;
+       while(<$catfile>) {
+               chomp;
+               push @lines, $_;
+       }
+       close($catfile);
+
+       return @lines;
+}
+
+sub git_ls_tree {
+       my ($rev, $filename) = @_;
+
+       my $lstree = open_pipe("git","ls-tree",$rev,$filename)
+               or die "Failed to call git ls-tree: $!";
+
+       my ($mode, $type, $blob, $tfilename);
+       while(<$lstree>) {
+               chomp;
+               ($mode, $type, $blob, $tfilename) = split(/\s+/, $_, 4);
+               last if ($tfilename eq $filename);
+       }
+       close($lstree);
+
+       return $blob if ($tfilename eq $filename);
+       die "git-ls-tree failed to find blob for $filename";
+
+}
+
+
+
+sub claim_line {
+       my ($floffset, $rev, $lines, %revinfo) = @_;
+       my $oline = get_line($lines, $floffset);
+       @{$lines->[$floffset]} = ( $oline, $rev,
+               $revinfo{'author'}, $revinfo{'author_date'} );
+       #printf("Claiming line %d with rev %s: '%s'\n",
+       #               $floffset, $rev, $oline) if 1;
+}
+
+sub git_commit_info {
+       my ($rev) = @_;
+       my $commit = open_pipe("git-cat-file", "commit", $rev)
+               or die "Failed to call git-cat-file: $!";
+
+       my %info;
+       while(<$commit>) {
+               chomp;
+               last if (length $_ == 0);
+
+               if (m/^author (.*) <(.*)> (.*)$/) {
+                       $info{'author'} = $1;
+                       $info{'author_email'} = $2;
+                       $info{'author_date'} = $3;
+               } elsif (m/^committer (.*) <(.*)> (.*)$/) {
+                       $info{'committer'} = $1;
+                       $info{'committer_email'} = $2;
+                       $info{'committer_date'} = $3;
+               }
+       }
+       close($commit);
+
+       return %info;
+}
+
+sub format_date {
+       if ($rawtime) {
+               return $_[0];
+       }
+       my ($timestamp, $timezone) = split(' ', $_[0]);
+       my $minutes = abs($timezone);
+       $minutes = int($minutes / 100) * 60 + ($minutes % 100);
+       if ($timezone < 0) {
+           $minutes = -$minutes;
+       }
+       my $t = $timestamp + $minutes * 60;
+       return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($t));
+}
+
+# Copied from git-send-email.perl - We need a Git.pm module..
+sub gitvar {
+    my ($var) = @_;
+    my $fh;
+    my $pid = open($fh, '-|');
+    die "$!" unless defined $pid;
+    if (!$pid) {
+       exec('git-var', $var) or die "$!";
+    }
+    my ($val) = <$fh>;
+    close $fh or die "$!";
+    chomp($val);
+    return $val;
+}
+
+sub gitvar_name {
+    my ($name) = @_;
+    my $val = gitvar($name);
+    my @field = split(/\s+/, $val);
+    return join(' ', @field[0...(@field-4)]);
+}
+
+sub open_pipe {
+       if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
+               return open_pipe_activestate(@_);
+       } else {
+               return open_pipe_normal(@_);
+       }
+}
+
+sub open_pipe_activestate {
+       tie *fh, "Git::ActiveStatePipe", @_;
+       return *fh;
+}
+
+sub open_pipe_normal {
+       my (@execlist) = @_;
+
+       my $pid = open my $kid, "-|";
+       defined $pid or die "Cannot fork: $!";
+
+       unless ($pid) {
+               exec @execlist;
+               die "Cannot exec @execlist: $!";
+       }
+
+       return $kid;
+}
+
+package Git::ActiveStatePipe;
+use strict;
+
+sub TIEHANDLE {
+       my ($class, @params) = @_;
+       my $cmdline = join " ", @params;
+       my  @data = qx{$cmdline};
+       bless { i => 0, data => \@data }, $class;
+}
+
+sub READLINE {
+       my $self = shift;
+       if ($self->{i} >= scalar @{$self->{data}}) {
+               return undef;
+       }
+       return $self->{'data'}->[ $self->{i}++ ];
+}
+
+sub CLOSE {
+       my $self = shift;
+       delete $self->{data};
+       delete $self->{i};
+}
+
+sub EOF {
+       my $self = shift;
+       return ($self->{i} >= scalar @{$self->{data}});
+}
diff --git a/git-applymbox.sh b/git-applymbox.sh
new file mode 100755 (executable)
index 0000000..5569fdc
--- /dev/null
@@ -0,0 +1,116 @@
+#!/bin/sh
+##
+## "dotest" is my stupid name for my patch-application script, which
+## I never got around to renaming after I tested it. We're now on the
+## second generation of scripts, still called "dotest".
+##
+## Update: Ryan Anderson finally shamed me into naming this "applymbox".
+##
+## You give it a mbox-format collection of emails, and it will try to
+## apply them to the kernel using "applypatch"
+##
+## The patch application may fail in the middle.  In which case:
+## (1) look at .dotest/patch and fix it up to apply
+## (2) re-run applymbox with -c .dotest/msg-number for the current one.
+## Pay a special attention to the commit log message if you do this and
+## use a Signoff_file, because applypatch wants to append the sign-off
+## message to msg-clean every time it is run.
+##
+## git-am is supposed to be the newer and better tool for this job.
+
+USAGE='[-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]'
+. git-sh-setup
+
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+keep_subject= query_apply= continue= utf8= resume=t
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -u)     utf8=-u ;;
+       -k)     keep_subject=-k ;;
+       -q)     query_apply=t ;;
+       -c)     continue="$2"; resume=f; shift ;;
+       -m)     fall_back_3way=t ;;
+       -*)     usage ;;
+       *)      break ;;
+       esac
+       shift
+done
+
+case "$continue" in
+'')
+       rm -rf .dotest
+       mkdir .dotest
+       num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
+       echo "$num_msgs patch(es) to process."
+       shift
+esac
+
+files=$(git-diff-index --cached --name-only HEAD) || exit
+if [ "$files" ]; then
+   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+   exit 1
+fi
+
+case "$query_apply" in
+t)     touch .dotest/.query_apply
+esac
+case "$fall_back_3way" in
+t)     : >.dotest/.3way
+esac
+case "$keep_subject" in
+-k)    : >.dotest/.keep_subject
+esac
+
+signoff="$1"
+set x .dotest/0*
+shift
+while case "$#" in 0) break;; esac
+do
+    i="$1" 
+    case "$resume,$continue" in
+    f,$i)      resume=t;;
+    f,*)       shift
+               continue;;
+    *)
+           git-mailinfo $keep_subject $utf8 \
+               .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
+           git-stripspace < .dotest/msg > .dotest/msg-clean
+           ;;
+    esac
+    while :; # for fixing up and retry
+    do
+       git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
+       case "$?" in
+       0)
+               # Remove the cleanly applied one to reduce clutter.
+               rm -f .dotest/$i
+               ;;
+       2)
+               # 2 is a special exit code from applypatch to indicate that
+               # the patch wasn't applied, but continue anyway 
+               ;;
+       *)
+               ret=$?
+               if test -f .dotest/.query_apply
+               then
+                       echo >&2 "* Patch failed."
+                       echo >&2 "* You could fix it up in your editor and"
+                       echo >&2 "  retry.  If you want to do so, say yes here"
+                       echo >&2 "  AFTER fixing .dotest/patch up."
+                       echo >&2 -n "Retry [y/N]? "
+                       read yesno
+                       case "$yesno" in
+                       [Yy]*)
+                               continue ;;
+                       esac
+               fi
+               exit $ret
+       esac
+       break
+    done
+    shift
+done
+# return to pristine
+rm -fr .dotest
diff --git a/git-applypatch.sh b/git-applypatch.sh
new file mode 100755 (executable)
index 0000000..e4b0947
--- /dev/null
@@ -0,0 +1,212 @@
+#!/bin/sh
+##
+## applypatch takes four file arguments, and uses those to
+## apply the unpacked patch (surprise surprise) that they
+## represent to the current tree.
+##
+## The arguments are:
+##     $1 - file with commit message
+##     $2 - file with the actual patch
+##     $3 - "info" file with Author, email and subject
+##     $4 - optional file containing signoff to add
+##
+
+USAGE='<msg> <patch> <info> [<signoff>]'
+. git-sh-setup
+
+case "$#" in 3|4) ;; *) usage ;; esac
+
+final=.dotest/final-commit
+##
+## If this file exists, we ask before applying
+##
+query_apply=.dotest/.query_apply
+
+## We do not munge the first line of the commit message too much
+## if this file exists.
+keep_subject=.dotest/.keep_subject
+
+## We do not attempt the 3-way merge fallback unless this file exists.
+fall_back_3way=.dotest/.3way
+
+MSGFILE=$1
+PATCHFILE=$2
+INFO=$3
+SIGNOFF=$4
+EDIT=${VISUAL:-${EDITOR:-vi}}
+
+export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
+export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
+export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
+export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
+
+if test '' != "$SIGNOFF"
+then
+       if test -f "$SIGNOFF"
+       then
+               SIGNOFF=`cat "$SIGNOFF"` || exit
+       elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac
+       then
+               SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+                               s/>.*/>/
+                               s/^/Signed-off-by: /'
+               `
+       else
+               SIGNOFF=
+       fi
+       if test '' != "$SIGNOFF"
+       then
+               LAST_SIGNED_OFF_BY=`
+                       sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
+                       tail -n 1
+               `
+               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+                   test '' = "$LAST_SIGNED_OFF_BY" && echo
+                   echo "$SIGNOFF"
+               } >>"$MSGFILE"
+       fi
+fi
+
+patch_header=
+test -f "$keep_subject" || patch_header='[PATCH] '
+
+{
+       echo "$patch_header$SUBJECT"
+       if test -s "$MSGFILE"
+       then
+               echo
+               cat "$MSGFILE"
+       fi
+} >"$final"
+
+interactive=yes
+test -f "$query_apply" || interactive=no
+
+while [ "$interactive" = yes ]; do
+       echo "Commit Body is:"
+       echo "--------------------------"
+       cat "$final"
+       echo "--------------------------"
+       printf "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
+       read reply
+       case "$reply" in
+               y|Y) interactive=no;;
+               n|N) exit 2;;   # special value to tell dotest to keep going
+               e|E) "$EDIT" "$final";;
+               a|A) rm -f "$query_apply"
+                    interactive=no ;;
+       esac
+done
+
+if test -x "$GIT_DIR"/hooks/applypatch-msg
+then
+       "$GIT_DIR"/hooks/applypatch-msg "$final" || exit
+fi
+
+echo
+echo Applying "'$SUBJECT'"
+echo
+
+git-apply --index "$PATCHFILE" || {
+
+       # git-apply exits with status 1 when the patch does not apply,
+       # but it die()s with other failures, most notably upon corrupt
+       # patch.  In the latter case, there is no point to try applying
+       # it to another tree and do 3-way merge.
+       test $? = 1 || exit 1
+
+       test -f "$fall_back_3way" || exit 1
+
+       # Here if we know which revision the patch applies to,
+       # we create a temporary working tree and index, apply the
+       # patch, and attempt 3-way merge with the resulting tree.
+
+       O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+       rm -fr .patch-merge-*
+
+       if git-apply -z --index-info "$PATCHFILE" \
+               >.patch-merge-index-info 2>/dev/null &&
+               GIT_INDEX_FILE=.patch-merge-tmp-index \
+               git-update-index -z --index-info <.patch-merge-index-info &&
+               GIT_INDEX_FILE=.patch-merge-tmp-index \
+               git-write-tree >.patch-merge-tmp-base &&
+               (
+                       mkdir .patch-merge-tmp-dir &&
+                       cd .patch-merge-tmp-dir &&
+                       GIT_INDEX_FILE="../.patch-merge-tmp-index" \
+                       GIT_OBJECT_DIRECTORY="$O_OBJECT" \
+                       git-apply $binary --index
+               ) <"$PATCHFILE"
+       then
+               echo Using index info to reconstruct a base tree...
+               mv .patch-merge-tmp-base .patch-merge-base
+               mv .patch-merge-tmp-index .patch-merge-index
+       else
+       (
+               N=10
+
+               # Otherwise, try nearby trees that can be used to apply the
+               # patch.
+               git-rev-list --max-count=$N HEAD
+
+               # or hoping the patch is against known tags...
+               git-ls-remote --tags .
+       ) |
+           while read base junk
+           do
+               # Try it if we have it as a tree.
+               git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+               rm -fr .patch-merge-tmp-* &&
+               mkdir .patch-merge-tmp-dir || break
+               (
+                       cd .patch-merge-tmp-dir &&
+                       GIT_INDEX_FILE=../.patch-merge-tmp-index &&
+                       GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+                       export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+                       git-read-tree "$base" &&
+                       git-apply --index &&
+                       mv ../.patch-merge-tmp-index ../.patch-merge-index &&
+                       echo "$base" >../.patch-merge-base
+               ) <"$PATCHFILE"  2>/dev/null && break
+           done
+       fi
+
+       test -f .patch-merge-index &&
+       his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
+       orig_tree=$(cat .patch-merge-base) &&
+       rm -fr .patch-merge-* || exit 1
+
+       echo Falling back to patching base and 3-way merge using $orig_tree...
+
+       # This is not so wrong.  Depending on which base we picked,
+       # orig_tree may be wildly different from ours, but his_tree
+       # has the same set of wildly different changes in parts the
+       # patch did not touch, so resolve ends up cancelling them,
+       # saying that we reverted all those changes.
+
+       if git-merge-resolve $orig_tree -- HEAD $his_tree
+       then
+               echo Done.
+       else
+               echo Failed to merge in the changes.
+               exit 1
+       fi
+}
+
+if test -x "$GIT_DIR"/hooks/pre-applypatch
+then
+       "$GIT_DIR"/hooks/pre-applypatch || exit
+fi
+
+tree=$(git-write-tree) || exit 1
+echo Wrote tree $tree
+parent=$(git-rev-parse --verify HEAD) &&
+commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
+echo Committed: $commit
+git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
+
+if test -x "$GIT_DIR"/hooks/post-applypatch
+then
+       "$GIT_DIR"/hooks/post-applypatch
+fi
diff --git a/git-archimport.perl b/git-archimport.perl
new file mode 100755 (executable)
index 0000000..740bc1f
--- /dev/null
@@ -0,0 +1,1068 @@
+#!/usr/bin/perl -w
+#
+# This tool is copyright (c) 2005, Martin Langhoff.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to walk the output of tla abrowse, 
+# fetch the changesets and apply them. 
+#
+
+=head1 Invocation
+
+    git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] 
+       [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
+
+Imports a project from one or more Arch repositories. It will follow branches
+and repositories within the namespaces defined by the <archive/branch>
+parameters suppplied. If it cannot find the remote branch a merge comes from
+it will just import it as a regular commit. If it can find it, it will mark it 
+as a merge whenever possible.
+
+See man (1) git-archimport for more details.
+
+=head1 TODO
+
+ - create tag objects instead of ref tags
+ - audit shell-escaping of filenames
+ - hide our private tags somewhere smarter
+ - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines  
+ - sort and apply patches by graphing ancestry relations instead of just
+   relying in dates supplied in the changeset itself.
+   tla ancestry-graph -m could be helpful here...
+
+=head1 Devel tricks
+
+Add print in front of the shell commands invoked via backticks. 
+
+=head1 Devel Notes
+
+There are several places where Arch and git terminology are intermixed
+and potentially confused.
+
+The notion of a "branch" in git is approximately equivalent to
+a "archive/category--branch--version" in Arch.  Also, it should be noted
+that the "--branch" portion of "archive/category--branch--version" is really
+optional in Arch although not many people (nor tools!) seem to know this.
+This means that "archive/category--version" is also a valid "branch"
+in git terms.
+
+We always refer to Arch names by their fully qualified variant (which
+means the "archive" name is prefixed.
+
+For people unfamiliar with Arch, an "archive" is the term for "repository",
+and can contain multiple, unrelated branches.
+
+=cut
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Temp qw(tempdir);
+use File::Path qw(mkpath rmtree);
+use File::Basename qw(basename dirname);
+use Data::Dumper qw/ Dumper /;
+use IPC::Open2;
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$ENV{"GIT_DIR"} = $git_dir;
+my $ptag_dir = "$git_dir/archimport/tags";
+
+our($opt_h,$opt_f,$opt_v,$opt_T,$opt_t,$opt_D,$opt_a,$opt_o);
+
+sub usage() {
+    print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from Arch
+       [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] [ -D depth ] [ -t tempdir ]
+       repository/arch-branch [ repository/arch-branch] ...
+END
+    exit(1);
+}
+
+getopts("fThvat:D:") or usage();
+usage if $opt_h;
+
+@ARGV >= 1 or usage();
+# $arch_branches:
+# values associated with keys:
+#   =1 - Arch version / git 'branch' detected via abrowse on a limit
+#   >1 - Arch version / git 'branch' of an auxilliary branch we've merged
+my %arch_branches = map { $_ => 1 } @ARGV;
+
+$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";
+
+my %reachable = ();             # Arch repositories we can access
+my %unreachable = ();           # Arch repositories we can't access :<
+my @psets  = ();                # the collection
+my %psets  = ();                # the collection, by name
+my %stats  = (                 # Track which strategy we used to import:
+       get_tag => 0, replay => 0, get_new => 0, get_delta => 0,
+        simple_changeset => 0, import_or_tag => 0
+);
+
+my %rptags = ();                # my reverse private tags
+                                # to map a SHA1 to a commitid
+my $TLA = $ENV{'ARCH_CLIENT'} || 'tla';
+
+sub do_abrowse {
+    my $stage = shift;
+    while (my ($limit, $level) = each %arch_branches) {
+        next unless $level == $stage;
+        
+       open ABROWSE, "$TLA abrowse -fkD --merges $limit |" 
+                                or die "Problems with tla abrowse: $!";
+    
+        my %ps        = ();         # the current one
+        my $lastseen  = '';
+    
+        while (<ABROWSE>) {
+            chomp;
+            
+            # first record padded w 8 spaces
+            if (s/^\s{8}\b//) {
+                my ($id, $type) = split(m/\s+/, $_, 2);
+
+                my %last_ps;
+                # store the record we just captured
+                if (%ps && !exists $psets{ $ps{id} }) {
+                    %last_ps = %ps; # break references
+                    push (@psets, \%last_ps);
+                    $psets{ $last_ps{id} } = \%last_ps;
+                }
+                
+                my $branch = extract_versionname($id);
+                %ps = ( id => $id, branch => $branch );
+                if (%last_ps && ($last_ps{branch} eq $branch)) {
+                    $ps{parent_id} = $last_ps{id};
+                }
+                
+                $arch_branches{$branch} = 1;
+                $lastseen = 'id';
+
+                # deal with types (should work with baz or tla):
+                if ($type =~ m/\(.*changeset\)/) {
+                    $ps{type} = 's';
+                } elsif ($type =~ /\(.*import\)/) {
+                    $ps{type} = 'i';
+                } elsif ($type =~ m/\(tag.*?(\S+\@\S+).*?\)/) {
+                    $ps{type} = 't';
+                    # read which revision we've tagged when we parse the log
+                    $ps{tag}  = $1;
+                } else { 
+                    warn "Unknown type $type";
+                }
+
+                $arch_branches{$branch} = 1;
+                $lastseen = 'id';
+            } elsif (s/^\s{10}//) { 
+                # 10 leading spaces or more 
+                # indicate commit metadata
+                
+                # date
+                if ($lastseen eq 'id' && m/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)/){
+                    $ps{date}   = $1;
+                    $lastseen = 'date';
+                } elsif ($_ eq 'merges in:') {
+                    $ps{merges} = [];
+                    $lastseen = 'merges';
+                } elsif ($lastseen eq 'merges' && s/^\s{2}//) {
+                    my $id = $_;
+                    push (@{$ps{merges}}, $id);
+                   
+                    # aggressive branch finding:
+                    if ($opt_D) {
+                        my $branch = extract_versionname($id);
+                        my $repo = extract_reponame($branch);
+                        
+                        if (archive_reachable($repo) &&
+                                !defined $arch_branches{$branch}) {
+                            $arch_branches{$branch} = $stage + 1;
+                        }
+                    }
+                } else {
+                    warn "more metadata after merges!?: $_\n" unless /^\s*$/;
+                }
+            }
+        }
+
+        if (%ps && !exists $psets{ $ps{id} }) {
+            my %temp = %ps;         # break references
+            if (@psets && $psets[$#psets]{branch} eq $ps{branch}) {
+                $temp{parent_id} = $psets[$#psets]{id};
+            }
+            push (@psets, \%temp);  
+            $psets{ $temp{id} } = \%temp;
+        }    
+        
+        close ABROWSE or die "$TLA abrowse failed on $limit\n";
+    }
+}                               # end foreach $root
+
+do_abrowse(1);
+my $depth = 2;
+$opt_D ||= 0;
+while ($depth <= $opt_D) {
+    do_abrowse($depth);
+    $depth++;
+}
+
+## Order patches by time
+# FIXME see if we can find a more optimal way to do this by graphing
+# the ancestry data and walking it, that way we won't have to rely on
+# client-supplied dates
+@psets = sort {$a->{date}.$b->{id} cmp $b->{date}.$b->{id}} @psets;
+
+#print Dumper \@psets;
+
+##
+## TODO cleanup irrelevant patches
+##      and put an initial import
+##      or a full tag
+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`;
+       die $! if $?;
+       $import = 1;
+    } else {
+        die "Need to start from an import or a tag -- cannot use $psets[0]{id}";
+    }
+} else {    # progressing an import
+    # load the rptags
+    opendir(DIR, $ptag_dir)
+       || die "can't opendir: $!";
+    while (my $file = readdir(DIR)) {
+        # skip non-interesting-files
+        next unless -f "$ptag_dir/$file";
+   
+        # convert first '--' to '/' from old git-archimport to use
+        # as an archivename/c--b--v private tag
+        if ($file !~ m!,!) {
+            my $oldfile = $file;
+            $file =~ s!--!,!;
+            print STDERR "converting old tag $oldfile to $file\n";
+            rename("$ptag_dir/$oldfile", "$ptag_dir/$file") or die $!;
+        }
+       my $sha = ptag($file);
+       chomp $sha;
+       $rptags{$sha} = $file;
+    }
+    closedir DIR;
+}
+
+# process patchsets
+# extract the Arch repository name (Arch "archive" in Arch-speak)
+sub extract_reponame {
+    my $fq_cvbr = shift; # archivename/[[[[category]branch]version]revision]
+    return (split(/\//, $fq_cvbr))[0];
+}
+sub extract_versionname {
+    my $name = shift;
+    $name =~ s/--(?:patch|version(?:fix)?|base)-\d+$//;
+    return $name;
+}
+
+# convert a fully-qualified revision or version to a unique dirname:
+#   normalperson@yhbt.net-05/mpd--uclinux--1--patch-2 
+# becomes: normalperson@yhbt.net-05,mpd--uclinux--1
+#
+# the git notion of a branch is closer to
+# archive/category--branch--version than archive/category--branch, so we
+# use this to convert to git branch names.
+# Also, keep archive names but replace '/' with ',' since it won't require
+# subdirectories, and is safer than swapping '--' which could confuse
+# reverse-mapping when dealing with bastard branches that
+# are just archive/category--version  (no --branch)
+sub tree_dirname {
+    my $revision = shift;
+    my $name = extract_versionname($revision);
+    $name =~ s#/#,#;
+    return $name;
+}
+
+# old versions of git-archimport just use the <category--branch> part:
+sub old_style_branchname {
+    my $id = shift;
+    my $ret = safe_pipe_capture($TLA,'parse-package-name','-p',$id);
+    chomp $ret;
+    return $ret;
+}
+
+*git_branchname = $opt_o ? *old_style_branchname : *tree_dirname;
+
+sub process_patchset_accurate {
+    my $ps = shift;
+    
+    # switch to that branch if we're not already in that branch:
+    if (-e "$git_dir/refs/heads/$ps->{branch}") {
+       system('git-checkout','-f',$ps->{branch}) == 0 or die "$! $?\n";
+
+       # remove any old stuff that got leftover:
+       my $rm = safe_pipe_capture('git-ls-files','--others','-z');
+       rmtree(split(/\0/,$rm)) if $rm;
+    }
+    
+    # Apply the import/changeset/merge into the working tree
+    my $dir = sync_to_ps($ps);
+    # read the new log entry:
+    my @commitlog = safe_pipe_capture($TLA,'cat-log','-d',$dir,$ps->{id});
+    die "Error in cat-log: $!" if $?;
+    chomp @commitlog;
+
+    # grab variables we want from the log, new fields get added to $ps:
+    # (author, date, email, summary, message body ...)
+    parselog($ps, \@commitlog);
+
+    if ($ps->{id} =~ /--base-0$/ && $ps->{id} ne $psets[0]{id}) {
+        # this should work when importing continuations 
+        if ($ps->{tag} && (my $branchpoint = eval { ptag($ps->{tag}) })) {
+            
+            # find where we are supposed to branch from
+            system('git-checkout','-f','-b',$ps->{branch},
+                            $branchpoint) == 0 or die "$! $?\n";
+            
+            # remove any old stuff that got leftover:
+            my $rm = safe_pipe_capture('git-ls-files','--others','-z');
+            rmtree(split(/\0/,$rm)) if $rm;
+
+            # If we trust Arch with the fact that this is just 
+            # a tag, and it does not affect the state of the tree
+            # then we just tag and move on
+            tag($ps->{id}, $branchpoint);
+            ptag($ps->{id}, $branchpoint);
+            print " * Tagged $ps->{id} at $branchpoint\n";
+            return 0;
+        } else {
+            warn "Tagging from unknown id unsupported\n" if $ps->{tag};
+        }
+        # allow multiple bases/imports here since Arch supports cherry-picks
+        # from unrelated trees
+    } 
+    
+    # update the index with all the changes we got
+    system('git-diff-files --name-only -z | '.
+            'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
+    system('git-ls-files --others -z | '.
+            'git-update-index --add -z --stdin') == 0 or die "$! $?\n";
+    return 1;
+}
+
+# the native changeset processing strategy.  This is very fast, but
+# does not handle permissions or any renames involving directories
+sub process_patchset_fast {
+    my $ps = shift;
+    # 
+    # create the branch if needed
+    #
+    if ($ps->{type} eq 'i' && !$import) {
+        die "Should not have more than one 'Initial import' per GIT import: $ps->{id}";
+    }
+
+    unless ($import) { # skip for import
+        if ( -e "$git_dir/refs/heads/$ps->{branch}") {
+            # we know about this branch
+            system('git-checkout',$ps->{branch});
+        } else {
+            # new branch! we need to verify a few things
+            die "Branch on a non-tag!" unless $ps->{type} eq 't';
+            my $branchpoint = ptag($ps->{tag});
+            die "Tagging from unknown id unsupported: $ps->{tag}" 
+                unless $branchpoint;
+            
+            # find where we are supposed to branch from
+            system('git-checkout','-b',$ps->{branch},$branchpoint);
+
+            # If we trust Arch with the fact that this is just 
+            # a tag, and it does not affect the state of the tree
+            # then we just tag and move on
+            tag($ps->{id}, $branchpoint);
+            ptag($ps->{id}, $branchpoint);
+            print " * Tagged $ps->{id} at $branchpoint\n";
+            return 0;
+        } 
+        die $! if $?;
+    } 
+
+    #
+    # Apply the import/changeset/merge into the working tree
+    # 
+    if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
+        apply_import($ps) or die $!;
+        $stats{import_or_tag}++;
+        $import=0;
+    } elsif ($ps->{type} eq 's') {
+        apply_cset($ps);
+        $stats{simple_changeset}++;
+    }
+
+    #
+    # prepare update git's index, based on what arch knows
+    # about the pset, resolve parents, etc
+    #
+    
+    my @commitlog = safe_pipe_capture($TLA,'cat-archive-log',$ps->{id}); 
+    die "Error in cat-archive-log: $!" if $?;
+        
+    parselog($ps,\@commitlog);
+
+    # imports don't give us good info
+    # on added files. Shame on them
+    if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
+        system('git-ls-files --deleted -z | '.
+                'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
+        system('git-ls-files --others -z | '.
+                'git-update-index --add -z --stdin') == 0 or die "$! $?\n";
+    }
+
+    # TODO: handle removed_directories and renamed_directories:
+
+    if (my $del = $ps->{removed_files}) {
+        unlink @$del;
+        while (@$del) {
+            my @slice = splice(@$del, 0, 100);
+            system('git-update-index','--remove','--',@slice) == 0 or
+                            die "Error in git-update-index --remove: $! $?\n";
+        }
+    }
+
+    if (my $ren = $ps->{renamed_files}) {                # renamed
+        if (@$ren % 2) {
+            die "Odd number of entries in rename!?";
+        }
+        
+        while (@$ren) {
+            my $from = shift @$ren;
+            my $to   = shift @$ren;           
+
+            unless (-d dirname($to)) {
+                mkpath(dirname($to)); # will die on err
+            }
+            # print "moving $from $to";
+            rename($from, $to) or die "Error renaming '$from' '$to': $!\n";
+            system('git-update-index','--remove','--',$from) == 0 or
+                            die "Error in git-update-index --remove: $! $?\n";
+            system('git-update-index','--add','--',$to) == 0 or
+                            die "Error in git-update-index --add: $! $?\n";
+        }
+    }
+
+    if (my $add = $ps->{new_files}) {
+        while (@$add) {
+            my @slice = splice(@$add, 0, 100);
+            system('git-update-index','--add','--',@slice) == 0 or
+                            die "Error in git-update-index --add: $! $?\n";
+        }
+    }
+
+    if (my $mod = $ps->{modified_files}) {
+        while (@$mod) {
+            my @slice = splice(@$mod, 0, 100);
+            system('git-update-index','--',@slice) == 0 or
+                            die "Error in git-update-index: $! $?\n";
+        }
+    }
+    return 1; # we successfully applied the changeset
+}
+
+if ($opt_f) {
+    print "Will import patchsets using the fast strategy\n",
+            "Renamed directories and permission changes will be missed\n";
+    *process_patchset = *process_patchset_fast;
+} else {
+    print "Using the default (accurate) import strategy.\n",
+            "Things may be a bit slow\n";
+    *process_patchset = *process_patchset_accurate;
+}
+    
+foreach my $ps (@psets) {
+    # process patchsets
+    $ps->{branch} = git_branchname($ps->{id});
+
+    #
+    # ensure we have a clean state 
+    # 
+    if (my $dirty = `git-diff-files`) {
+        die "Unclean tree when about to process $ps->{id} " .
+            " - did we fail to commit cleanly before?\n$dirty";
+    }
+    die $! if $?;
+    
+    #
+    # skip commits already in repo
+    #
+    if (ptag($ps->{id})) {
+      $opt_v && print " * Skipping already imported: $ps->{id}\n";
+      next;
+    }
+
+    print " * Starting to work on $ps->{id}\n";
+
+    process_patchset($ps) or next;
+
+    # warn "errors when running git-update-index! $!";
+    my $tree = `git-write-tree`;
+    die "cannot write tree $!" if $?;
+    chomp $tree;
+    
+    #
+    # Who's your daddy?
+    #
+    my @par;
+    if ( -e "$git_dir/refs/heads/$ps->{branch}") {
+        if (open HEAD, "<","$git_dir/refs/heads/$ps->{branch}") {
+            my $p = <HEAD>;
+            close HEAD;
+            chomp $p;
+            push @par, '-p', $p;
+        } else { 
+            if ($ps->{type} eq 's') {
+                warn "Could not find the right head for the branch $ps->{branch}";
+            }
+        }
+    }
+    
+    if ($ps->{merges}) {
+        push @par, find_parents($ps);
+    }
+
+    #    
+    # Commit, tag and clean state
+    #
+    $ENV{TZ}                  = 'GMT';
+    $ENV{GIT_AUTHOR_NAME}     = $ps->{author};
+    $ENV{GIT_AUTHOR_EMAIL}    = $ps->{email};
+    $ENV{GIT_AUTHOR_DATE}     = $ps->{date};
+    $ENV{GIT_COMMITTER_NAME}  = $ps->{author};
+    $ENV{GIT_COMMITTER_EMAIL} = $ps->{email};
+    $ENV{GIT_COMMITTER_DATE}  = $ps->{date};
+
+    my $pid = open2(*READER, *WRITER,'git-commit-tree',$tree,@par) 
+        or die $!;
+    print WRITER $ps->{summary},"\n";
+    print WRITER $ps->{message},"\n";
+    
+    # make it easy to backtrack and figure out which Arch revision this was:
+    print WRITER 'git-archimport-id: ',$ps->{id},"\n";
+    
+    close WRITER;
+    my $commitid = <READER>;    # read
+    chomp $commitid;
+    close READER;
+    waitpid $pid,0;             # close;
+
+    if (length $commitid != 40) {
+        die "Something went wrong with the commit! $! $commitid";
+    }
+    #
+    # Update the branch
+    # 
+    open  HEAD, ">","$git_dir/refs/heads/$ps->{branch}";
+    print HEAD $commitid;
+    close HEAD;
+    system('git-update-ref', 'HEAD', "$ps->{branch}");
+
+    # tag accordingly
+    ptag($ps->{id}, $commitid); # private tag
+    if ($opt_T || $ps->{type} eq 't' || $ps->{type} eq 'i') {
+        tag($ps->{id}, $commitid);
+    }
+    print " * Committed $ps->{id}\n";
+    print "   + tree   $tree\n";
+    print "   + commit $commitid\n";
+    $opt_v && print "   + commit date is  $ps->{date} \n";
+    $opt_v && print "   + parents:  ",join(' ',@par),"\n";
+}
+
+if ($opt_v) {
+    foreach (sort keys %stats) {
+        print" $_: $stats{$_}\n";
+    }
+}
+exit 0;
+
+# used by the accurate strategy:
+sub sync_to_ps {
+    my $ps = shift;
+    my $tree_dir = $tmp.'/'.tree_dirname($ps->{id});
+    
+    $opt_v && print "sync_to_ps($ps->{id}) method: ";
+
+    if (-d $tree_dir) {
+        if ($ps->{type} eq 't') {
+           $opt_v && print "get (tag)\n";
+            # looks like a tag-only or (worse,) a mixed tags/changeset branch,
+            # can't rely on replay to work correctly on these
+            rmtree($tree_dir);
+            safe_pipe_capture($TLA,'get','--no-pristine',$ps->{id},$tree_dir);
+            $stats{get_tag}++;
+        } else {
+                my $tree_id = arch_tree_id($tree_dir);
+                if ($ps->{parent_id} && ($ps->{parent_id} eq $tree_id)) {
+                    # the common case (hopefully)
+                   $opt_v && print "replay\n";
+                    safe_pipe_capture($TLA,'replay','-d',$tree_dir,$ps->{id});
+                    $stats{replay}++;
+                } else {
+                    # getting one tree is usually faster than getting two trees
+                    # and applying the delta ...
+                    rmtree($tree_dir);
+                   $opt_v && print "apply-delta\n";
+                    safe_pipe_capture($TLA,'get','--no-pristine',
+                                        $ps->{id},$tree_dir);
+                    $stats{get_delta}++;
+                }
+        }
+    } else {
+        # new branch work
+        $opt_v && print "get (new tree)\n";
+        safe_pipe_capture($TLA,'get','--no-pristine',$ps->{id},$tree_dir);
+        $stats{get_new}++;
+    }
+   
+    # added -I flag to rsync since we're going to fast! AIEEEEE!!!!
+    system('rsync','-aI','--delete','--exclude',$git_dir,
+#               '--exclude','.arch-inventory',
+                '--exclude','.arch-ids','--exclude','{arch}',
+                '--exclude','+*','--exclude',',*',
+                "$tree_dir/",'./') == 0 or die "Cannot rsync $tree_dir: $! $?";
+    return $tree_dir;
+}
+
+sub apply_import {
+    my $ps = shift;
+    my $bname = git_branchname($ps->{id});
+
+    mkpath($tmp);
+
+    safe_pipe_capture($TLA,'get','-s','--no-pristine',$ps->{id},"$tmp/import");
+    die "Cannot get import: $!" if $?;    
+    system('rsync','-aI','--delete', '--exclude',$git_dir,
+               '--exclude','.arch-ids','--exclude','{arch}',
+               "$tmp/import/", './');
+    die "Cannot rsync import:$!" if $?;
+    
+    rmtree("$tmp/import");
+    die "Cannot remove tempdir: $!" if $?;
+    
+
+    return 1;
+}
+
+sub apply_cset {
+    my $ps = shift;
+
+    mkpath($tmp);
+
+    # get the changeset
+    safe_pipe_capture($TLA,'get-changeset',$ps->{id},"$tmp/changeset");
+    die "Cannot get changeset: $!" if $?;
+    
+    # apply patches
+    if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
+        # this can be sped up considerably by doing
+        #    (find | xargs cat) | patch
+        # but that cna get mucked up by patches
+        # with missing trailing newlines or the standard 
+        # 'missing newline' flag in the patch - possibly
+        # produced with an old/buggy diff.
+        # slow and safe, we invoke patch once per patchfile
+        `find $tmp/changeset/patches -type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`;
+        die "Problem applying patches! $!" if $?;
+    }
+
+    # apply changed binary files
+    if (my @modified = `find $tmp/changeset/patches -type f -name '*.modified'`) {
+        foreach my $mod (@modified) {
+            chomp $mod;
+            my $orig = $mod;
+            $orig =~ s/\.modified$//; # lazy
+            $orig =~ s!^\Q$tmp\E/changeset/patches/!!;
+            #print "rsync -p '$mod' '$orig'";
+            system('rsync','-p',$mod,"./$orig");
+            die "Problem applying binary changes! $!" if $?;
+        }
+    }
+
+    # bring in new files
+    system('rsync','-aI','--exclude',$git_dir,
+               '--exclude','.arch-ids',
+               '--exclude', '{arch}',
+               "$tmp/changeset/new-files-archive/",'./');
+
+    # deleted files are hinted from the commitlog processing
+
+    rmtree("$tmp/changeset");
+}
+
+
+# =for reference
+# notes: *-files/-directories keys cannot have spaces, they're always
+# pika-escaped.  Everything after the first newline
+# A log entry looks like:
+# Revision: moodle-org--moodle--1.3.3--patch-15
+# Archive: arch-eduforge@catalyst.net.nz--2004
+# Creator: Penny Leach <penny@catalyst.net.nz>
+# Date: Wed May 25 14:15:34 NZST 2005
+# Standard-date: 2005-05-25 02:15:34 GMT
+# New-files: lang/de/.arch-ids/block_glossary_random.php.id
+#     lang/de/.arch-ids/block_html.php.id
+# New-directories: lang/de/help/questionnaire
+#     lang/de/help/questionnaire/.arch-ids
+# Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id
+#    db_sears.sql db/db_sears.sql
+# Removed-files: lang/be/docs/.arch-ids/release.html.id
+#     lang/be/docs/.arch-ids/releaseold.html.id
+# Modified-files: admin/cron.php admin/delete.php
+#     admin/editor.html backup/lib.php backup/restore.php
+# New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15
+# Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+)
+#   summary can be multiline with a leading space just like the above fields
+# Keywords:
+#
+# Updating yadda tadda tadda madda
+sub parselog {
+    my ($ps, $log) = @_;
+    my $key = undef;
+
+    # headers we want that contain filenames:
+    my %want_headers = (
+        new_files => 1,
+        modified_files => 1,
+        renamed_files => 1,
+        renamed_directories => 1,
+        removed_files => 1,
+        removed_directories => 1,
+    );
+    
+    chomp (@$log);
+    while ($_ = shift @$log) {
+        if (/^Continuation-of:\s*(.*)/) {
+            $ps->{tag} = $1;
+            $key = undef;
+        } elsif (/^Summary:\s*(.*)$/ ) {
+            # summary can be multiline as long as it has a leading space
+            $ps->{summary} = [ $1 ];
+            $key = 'summary';
+        } elsif (/^Creator: (.*)\s*<([^\>]+)>/) {
+            $ps->{author} = $1;
+            $ps->{email} = $2;
+            $key = undef;
+        # any *-files or *-directories can be read here:
+        } elsif (/^([A-Z][a-z\-]+):\s*(.*)$/) {
+            my $val = $2;
+            $key = lc $1;
+            $key =~ tr/-/_/; # too lazy to quote :P
+            if ($want_headers{$key}) {
+                push @{$ps->{$key}}, split(/\s+/, $val);
+            } else {
+                $key = undef;
+            }
+        } elsif (/^$/) {
+            last; # remainder of @$log that didn't get shifted off is message
+        } elsif ($key) {
+            if (/^\s+(.*)$/) {
+                if ($key eq 'summary') {
+                    push @{$ps->{$key}}, $1;
+                } else { # files/directories:
+                    push @{$ps->{$key}}, split(/\s+/, $1);
+                }
+            } else {
+                $key = undef;
+            }
+        }
+    }
+   
+    # post-processing:
+    $ps->{summary} = join("\n",@{$ps->{summary}})."\n";
+    $ps->{message} = join("\n",@$log);
+    
+    # skip Arch control files, unescape pika-escaped files
+    foreach my $k (keys %want_headers) {
+        next unless (defined $ps->{$k});
+        my @tmp = ();
+        foreach my $t (@{$ps->{$k}}) {
+           next unless length ($t);
+           next if $t =~ m!\{arch\}/!;
+           next if $t =~ m!\.arch-ids/!;
+           # should we skip this?
+           next if $t =~ m!\.arch-inventory$!;
+           # tla cat-archive-log will give us filenames with spaces as file\(sp)name - why?
+           # we can assume that any filename with \ indicates some pika escaping that we want to get rid of.
+           if ($t =~ /\\/ ){
+               $t = (safe_pipe_capture($TLA,'escape','--unescaped',$t))[0];
+           }
+           push @tmp, $t;
+        }
+        $ps->{$k} = \@tmp;
+    }
+}
+
+# write/read a tag
+sub tag {
+    my ($tag, $commit) = @_;
+    if ($opt_o) {
+        $tag =~ s|/|--|g;
+    } else {
+        # don't use subdirs for tags yet, it could screw up other porcelains
+        $tag =~ s|/|,|g;
+    }
+    
+    if ($commit) {
+        open(C,">","$git_dir/refs/tags/$tag")
+            or die "Cannot create tag $tag: $!\n";
+        print C "$commit\n"
+            or die "Cannot write tag $tag: $!\n";
+        close(C)
+            or die "Cannot write tag $tag: $!\n";
+        print " * Created tag '$tag' on '$commit'\n" if $opt_v;
+    } else {                    # read
+        open(C,"<","$git_dir/refs/tags/$tag")
+            or die "Cannot read tag $tag: $!\n";
+        $commit = <C>;
+        chomp $commit;
+        die "Error reading tag $tag: $!\n" unless length $commit == 40;
+        close(C)
+            or die "Cannot read tag $tag: $!\n";
+        return $commit;
+    }
+}
+
+# write/read a private tag
+# reads fail softly if the tag isn't there
+sub ptag {
+    my ($tag, $commit) = @_;
+
+    # don't use subdirs for tags yet, it could screw up other porcelains
+    $tag =~ s|/|,|g; 
+    
+    my $tag_file = "$ptag_dir/$tag";
+    my $tag_branch_dir = dirname($tag_file);
+    mkpath($tag_branch_dir) unless (-d $tag_branch_dir);
+
+    if ($commit) {              # write
+        open(C,">",$tag_file)
+            or die "Cannot create tag $tag: $!\n";
+        print C "$commit\n"
+            or die "Cannot write tag $tag: $!\n";
+        close(C)
+            or die "Cannot write tag $tag: $!\n";
+       $rptags{$commit} = $tag 
+           unless $tag =~ m/--base-0$/;
+    } else {                    # read
+        # if the tag isn't there, return 0
+        unless ( -s $tag_file) {
+            return 0;
+        }
+        open(C,"<",$tag_file)
+            or die "Cannot read tag $tag: $!\n";
+        $commit = <C>;
+        chomp $commit;
+        die "Error reading tag $tag: $!\n" unless length $commit == 40;
+        close(C)
+            or die "Cannot read tag $tag: $!\n";
+       unless (defined $rptags{$commit}) {
+           $rptags{$commit} = $tag;
+       }
+        return $commit;
+    }
+}
+
+sub find_parents {
+    #
+    # Identify what branches are merging into me
+    # and whether we are fully merged
+    # git-merge-base <headsha> <headsha> should tell
+    # me what the base of the merge should be 
+    #
+    my $ps = shift;
+
+    my %branches; # holds an arrayref per branch
+                  # the arrayref contains a list of
+                  # merged patches between the base
+                  # of the merge and the current head
+
+    my @parents;  # parents found for this commit
+
+    # simple loop to split the merges
+    # per branch
+    foreach my $merge (@{$ps->{merges}}) {
+       my $branch = git_branchname($merge);
+       unless (defined $branches{$branch} ){
+           $branches{$branch} = [];
+       }
+       push @{$branches{$branch}}, $merge;
+    }
+
+    #
+    # foreach branch find a merge base and walk it to the 
+    # head where we are, collecting the merged patchsets that
+    # Arch has recorded. Keep that in @have
+    # Compare that with the commits on the other branch
+    # between merge-base and the tip of the branch (@need)
+    # and see if we have a series of consecutive patches
+    # starting from the merge base. The tip of the series
+    # of consecutive patches merged is our new parent for 
+    # that branch.
+    #
+    foreach my $branch (keys %branches) {
+
+       # check that we actually know about the branch
+       next unless -e "$git_dir/refs/heads/$branch";
+
+       my $mergebase = `git-merge-base $branch $ps->{branch}`;
+       if ($?) { 
+           # Don't die here, Arch supports one-way cherry-picking
+           # between branches with no common base (or any relationship
+           # at all beforehand)
+           warn "Cannot find merge base for $branch and $ps->{branch}";
+           next;
+       }
+       chomp $mergebase;
+
+       # now walk up to the mergepoint collecting what patches we have
+       my $branchtip = git_rev_parse($ps->{branch});
+       my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`;
+       my %have; # collected merges this branch has
+       foreach my $merge (@{$ps->{merges}}) {
+           $have{$merge} = 1;
+       }
+       my %ancestorshave;
+       foreach my $par (@ancestors) {
+           $par = commitid2pset($par);
+           if (defined $par->{merges}) {
+               foreach my $merge (@{$par->{merges}}) {
+                   $ancestorshave{$merge}=1;
+               }
+           }
+       }
+       # print "++++ Merges in $ps->{id} are....\n";
+       # my @have = sort keys %have;   print Dumper(\@have);
+
+       # merge what we have with what ancestors have
+       %have = (%have, %ancestorshave);
+
+       # see what the remote branch has - these are the merges we 
+       # will want to have in a consecutive series from the mergebase
+       my $otherbranchtip = git_rev_parse($branch);
+       my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
+       my @need;
+       foreach my $needps (@needraw) {         # get the psets
+           $needps = commitid2pset($needps);
+           # git-rev-list will also
+           # list commits merged in via earlier 
+           # merges. we are only interested in commits
+           # from the branch we're looking at
+           if ($branch eq $needps->{branch}) {
+               push @need, $needps->{id};
+           }
+       }
+
+       # print "++++ Merges from $branch we want are....\n";
+       # print Dumper(\@need);
+
+       my $newparent;
+       while (my $needed_commit = pop @need) {
+           if ($have{$needed_commit}) {
+               $newparent = $needed_commit;
+           } else {
+               last; # break out of the while
+           }
+       }
+       if ($newparent) {
+           push @parents, $newparent;
+       }
+
+
+    } # end foreach branch
+
+    # prune redundant parents
+    my %parents;
+    foreach my $p (@parents) {
+       $parents{$p} = 1;
+    }
+    foreach my $p (@parents) {
+       next unless exists $psets{$p}{merges};
+       next unless ref    $psets{$p}{merges};
+       my @merges = @{$psets{$p}{merges}};
+       foreach my $merge (@merges) {
+           if ($parents{$merge}) { 
+               delete $parents{$merge};
+           }
+       }
+    }
+
+    @parents = ();
+    foreach (keys %parents) {
+        push @parents, '-p', ptag($_);
+    }
+    return @parents;
+}
+
+sub git_rev_parse {
+    my $name = shift;
+    my $val  = `git-rev-parse $name`;
+    die "Error: git-rev-parse $name" if $?;
+    chomp $val;
+    return $val;
+}
+
+# resolve a SHA1 to a known patchset
+sub commitid2pset {
+    my $commitid = shift;
+    chomp $commitid;
+    my $name = $rptags{$commitid} 
+       || die "Cannot find reverse tag mapping for $commitid";
+    $name =~ s|,|/|;
+    my $ps   = $psets{$name} 
+       || (print Dumper(sort keys %psets)) && die "Cannot find patchset for $name";
+    return $ps;
+}
+
+
+# an alterative to `command` that allows input to be passed as an array
+# to work around shell problems with weird characters in arguments
+sub safe_pipe_capture {
+    my @output;
+    if (my $pid = open my $child, '-|') {
+        @output = (<$child>);
+        close $child or die join(' ',@_).": $! $?";
+    } else {
+       exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+    }
+    return wantarray ? @output : join('',@output);
+}
+
+# `tla logs -rf -d <dir> | head -n1` or `baz tree-id <dir>`
+sub arch_tree_id {
+    my $dir = shift;
+    chomp( my $ret = (safe_pipe_capture($TLA,'logs','-rf','-d',$dir))[0] );
+    return $ret;
+}
+
+sub archive_reachable {
+    my $archive = shift;
+    return 1 if $reachable{$archive};
+    return 0 if $unreachable{$archive};
+    
+    if (system "$TLA whereis-archive $archive >/dev/null") {
+        if ($opt_a && (system($TLA,'register-archive',
+                      "http://mirrors.sourcecontrol.net/$archive") == 0)) {
+            $reachable{$archive} = 1;
+            return 1;
+        }
+        print STDERR "Archive is unreachable: $archive\n";
+        $unreachable{$archive} = 1;
+        return 0;
+    } else {
+        $reachable{$archive} = 1;
+        return 1;
+    }
+}
+
diff --git a/git-bisect.sh b/git-bisect.sh
new file mode 100755 (executable)
index 0000000..03df143
--- /dev/null
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+USAGE='[start|bad|good|next|reset|visualize]'
+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.
+git bisect next                        find next bisection to test and check it out.
+git bisect reset [<branch>]    finish bisection search and go back to branch.
+git bisect visualize            show bisect status in gitk.
+git bisect replay <logfile>    replay bisection log
+git bisect log                 show bisect log.'
+
+. git-sh-setup
+
+sq() {
+       perl -e '
+               for (@ARGV) {
+                       s/'\''/'\'\\\\\'\''/g;
+                       print " '\''$_'\''";
+               }
+               print "\n";
+       ' "$@"
+}
+
+bisect_autostart() {
+       test -d "$GIT_DIR/refs/bisect" || {
+               echo >&2 'You need to start by "git bisect start"'
+               if test -t 0
+               then
+                       echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+                       read yesno
+                       case "$yesno" in
+                       [Nn]*)
+                               exit ;;
+                       esac
+                       bisect_start
+               else
+                       exit 1
+               fi
+       }
+}
+
+bisect_start() {
+       #
+       # Verify HEAD. If we were bisecting before this, reset to the
+       # top-of-line master first!
+       #
+       head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+       die "Bad HEAD - I need a symbolic ref"
+       case "$head" in
+       refs/heads/bisect*)
+               if [ -s "$GIT_DIR/head-name" ]; then
+                   branch=`cat "$GIT_DIR/head-name"`
+               else
+                   branch=master
+               fi
+               git checkout $branch || exit
+               ;;
+       refs/heads/*)
+               [ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
+               echo "$head" | sed 's#^refs/heads/##' >"$GIT_DIR/head-name"
+               ;;
+       *)
+               die "Bad HEAD - strange symbolic ref"
+               ;;
+       esac
+
+       #
+       # Get rid of any old bisect state
+       #
+       rm -f "$GIT_DIR/refs/heads/bisect"
+       rm -rf "$GIT_DIR/refs/bisect/"
+       mkdir "$GIT_DIR/refs/bisect"
+       {
+           printf "git-bisect start"
+           sq "$@"
+       } >"$GIT_DIR/BISECT_LOG"
+       sq "$@" >"$GIT_DIR/BISECT_NAMES"
+}
+
+bisect_bad() {
+       bisect_autostart
+       case "$#" in
+       0)
+               rev=$(git-rev-parse --verify HEAD) ;;
+       1)
+               rev=$(git-rev-parse --verify "$1") ;;
+       *)
+               usage ;;
+       esac || exit
+       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
+       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+       echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
+       bisect_auto_next
+}
+
+bisect_good() {
+       bisect_autostart
+        case "$#" in
+       0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
+       *)    revs=$(git-rev-parse --revs-only --no-flags "$@") &&
+               test '' != "$revs" || die "Bad rev input: $@" ;;
+       esac
+       for rev in $revs
+       do
+               rev=$(git-rev-parse --verify "$rev") || exit
+               echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+               echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+               echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
+       done
+       bisect_auto_next
+}
+
+bisect_next_check() {
+       next_ok=no
+        test -f "$GIT_DIR/refs/bisect/bad" &&
+       case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
+       refs/bisect/good-\*) ;;
+       *) next_ok=yes ;;
+       esac
+       case "$next_ok,$1" in
+       no,) false ;;
+       no,fail)
+           echo >&2 'You need to give me at least one good and one bad revisions.'
+           exit 1 ;;
+       *)
+           true ;;
+       esac
+}
+
+bisect_auto_next() {
+       bisect_next_check && bisect_next || :
+}
+
+bisect_next() {
+        case "$#" in 0) ;; *) usage ;; esac
+       bisect_autostart
+       bisect_next_check fail
+       bad=$(git-rev-parse --verify refs/bisect/bad) &&
+       good=$(git-rev-parse --sq --revs-only --not \
+               $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
+       rev=$(eval "git-rev-list --bisect $good $bad -- $(cat $GIT_DIR/BISECT_NAMES)") || exit
+       if [ -z "$rev" ]; then
+           echo "$bad was both good and bad"
+           exit 1
+       fi
+       if [ "$rev" = "$bad" ]; then
+           echo "$rev is first bad commit"
+           git-diff-tree --pretty $rev
+           exit 0
+       fi
+       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
+       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"
+}
+
+bisect_visualize() {
+       bisect_next_check fail
+       not=`cd "$GIT_DIR/refs" && echo bisect/good-*`
+       eval gitk bisect/bad --not $not -- $(cat "$GIT_DIR/BISECT_NAMES")
+}
+
+bisect_reset() {
+       case "$#" in
+       0) if [ -s "$GIT_DIR/head-name" ]; then
+              branch=`cat "$GIT_DIR/head-name"`
+          else
+              branch=master
+          fi ;;
+       1) test -f "$GIT_DIR/refs/heads/$1" || {
+              echo >&2 "$1 does not seem to be a valid branch"
+              exit 1
+          }
+          branch="$1" ;;
+        *)
+           usage ;;
+       esac
+       git checkout "$branch" &&
+       rm -fr "$GIT_DIR/refs/bisect"
+       rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
+       rm -f "$GIT_DIR/BISECT_LOG"
+       rm -f "$GIT_DIR/BISECT_NAMES"
+}
+
+bisect_replay () {
+       test -r "$1" || {
+               echo >&2 "cannot read $1 for replaying"
+               exit 1
+       }
+       bisect_reset
+       while read bisect command rev
+       do
+               test "$bisect" = "git-bisect" || continue
+               case "$command" in
+               start)
+                       cmd="bisect_start $rev"
+                       eval "$cmd"
+                       ;;
+               good)
+                       echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+                       echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+                       echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
+                       ;;
+               bad)
+                       echo "$rev" >"$GIT_DIR/refs/bisect/bad"
+                       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+                       echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
+                       ;;
+               *)
+                       echo >&2 "?? what are you talking about?"
+                       exit 1 ;;
+               esac
+       done <"$1"
+       bisect_auto_next
+}
+
+case "$#" in
+0)
+    usage ;;
+*)
+    cmd="$1"
+    shift
+    case "$cmd" in
+    start)
+        bisect_start "$@" ;;
+    bad)
+        bisect_bad "$@" ;;
+    good)
+        bisect_good "$@" ;;
+    next)
+        # Not sure we want "next" at the UI level anymore.
+        bisect_next "$@" ;;
+    visualize)
+       bisect_visualize "$@" ;;
+    reset)
+        bisect_reset "$@" ;;
+    replay)
+       bisect_replay "$@" ;;
+    log)
+       cat "$GIT_DIR/BISECT_LOG" ;;
+    *)
+        usage ;;
+    esac
+esac
diff --git a/git-branch.sh b/git-branch.sh
new file mode 100755 (executable)
index 0000000..e0501ec
--- /dev/null
@@ -0,0 +1,130 @@
+#!/bin/sh
+
+USAGE='[-l] [(-d | -D) <branchname>] | [[-f] <branchname> [<start-point>]] | -r'
+LONG_USAGE='If no arguments, show available branches and mark current branch with a star.
+If one argument, create a new branch <branchname> based off of current HEAD.
+If two arguments, create a new branch <branchname> based off of <start-point>.'
+
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+headref=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
+
+delete_branch () {
+    option="$1"
+    shift
+    for branch_name
+    do
+       case ",$headref," in
+       ",$branch_name,")
+           die "Cannot delete the branch you are on." ;;
+       ,,)
+           die "What branch are you on anyway?" ;;
+       esac
+       branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
+           branch=$(git-rev-parse --verify "$branch^0") ||
+               die "Seriously, what branch are you talking about?"
+       case "$option" in
+       -D)
+           ;;
+       *)
+           mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
+           case " $mbs " in
+           *' '$branch' '*)
+               # the merge base of branch and HEAD contains branch --
+               # which means that the HEAD contains everything in both.
+               ;;
+           *)
+               echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
+If you are sure you want to delete it, run 'git branch -D $branch_name'."
+               exit 1
+               ;;
+           esac
+           ;;
+       esac
+       rm -f "$GIT_DIR/logs/refs/heads/$branch_name"
+       rm -f "$GIT_DIR/refs/heads/$branch_name"
+       echo "Deleted branch $branch_name."
+    done
+    exit 0
+}
+
+ls_remote_branches () {
+    git-rev-parse --symbolic --all |
+    sed -ne 's|^refs/\(remotes/\)|\1|p' |
+    sort
+}
+
+force=
+create_log=
+while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
+do
+       case "$1" in
+       -d | -D)
+               delete_branch "$@"
+               exit
+               ;;
+       -r)
+               ls_remote_branches
+               exit
+               ;;
+       -f)
+               force="$1"
+               ;;
+       -l)
+               create_log="yes"
+               ;;
+       --)
+               shift
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       esac
+       shift
+done
+
+case "$#" in
+0)
+       git-rev-parse --symbolic --branches |
+       sort |
+       while read ref
+       do
+               if test "$headref" = "$ref"
+               then
+                       pfx='*'
+               else
+                       pfx=' '
+               fi
+               echo "$pfx $ref"
+       done
+       exit 0 ;;
+1)
+       head=HEAD ;;
+2)
+       head="$2^0" ;;
+esac
+branchname="$1"
+
+rev=$(git-rev-parse --verify "$head") || exit
+
+git-check-ref-format "heads/$branchname" ||
+       die "we do not like '$branchname' as a branch name."
+
+if [ -e "$GIT_DIR/refs/heads/$branchname" ]
+then
+       if test '' = "$force"
+       then
+               die "$branchname already exists."
+       elif test "$branchname" = "$headref"
+       then
+               die "cannot force-update the current branch."
+       fi
+fi
+if test "$create_log" = 'yes'
+then
+       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$branchname")
+       touch "$GIT_DIR/logs/refs/heads/$branchname"
+fi
+git update-ref -m "branch: Created from $head" "refs/heads/$branchname" $rev
diff --git a/git-checkout.sh b/git-checkout.sh
new file mode 100755 (executable)
index 0000000..564117f
--- /dev/null
@@ -0,0 +1,211 @@
+#!/bin/sh
+
+USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+SUBDIRECTORY_OK=Sometimes
+. git-sh-setup
+
+old=$(git-rev-parse HEAD)
+old_name=HEAD
+new=
+new_name=
+force=
+branch=
+newbranch=
+newbranch_log=
+merge=
+while [ "$#" != "0" ]; do
+    arg="$1"
+    shift
+    case "$arg" in
+       "-b")
+               newbranch="$1"
+               shift
+               [ -z "$newbranch" ] &&
+                       die "git checkout: -b needs a branch name"
+               [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+                       die "git checkout: branch $newbranch already exists"
+               git-check-ref-format "heads/$newbranch" ||
+                       die "git checkout: we do not like '$newbranch' as a branch name."
+               ;;
+       "-l")
+               newbranch_log=1
+               ;;
+       "-f")
+               force=1
+               ;;
+       -m)
+               merge=1
+               ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+               then
+                       if [ -z "$rev" ]; then
+                               echo "unknown flag $arg"
+                               exit 1
+                       fi
+                       new="$rev"
+                       new_name="$arg^0"
+                       if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+                               branch="$arg"
+                       fi
+               elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+               then
+                       # checking out selected paths from a tree-ish.
+                       new="$rev"
+                       new_name="$arg^{tree}"
+                       branch=
+               else
+                       new=
+                       new_name=
+                       branch=
+                       set x "$arg" "$@"
+                       shift
+               fi
+               break
+               ;;
+    esac
+done
+
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches.  This is the traditional behaviour.
+#
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+       hint=
+       if test "$#" -eq 1
+       then
+               hint="
+Did you intend to checkout '$@' which can not be resolved as commit?"
+       fi
+       if test '' != "$newbranch$force$merge"
+       then
+               die "git checkout: updating paths is incompatible with switching branches/forcing$hint"
+       fi
+       if test '' != "$new"
+       then
+               # from a specific tree-ish; note that this is for
+               # rescuing paths and is never meant to remove what
+               # is not in the named tree-ish.
+               git-ls-tree --full-name -r "$new" "$@" |
+               git-update-index --index-info || exit $?
+       fi
+       git-checkout-index -f -u -- "$@"
+       exit $?
+else
+       # Make sure we did not fall back on $arg^{tree} codepath
+       # since we are not checking out from an arbitrary tree-ish,
+       # but switching branches.
+       if test '' != "$new"
+       then
+               git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               die "Cannot switch branch to a non-commit."
+       fi
+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
+
+[ -z "$new" ] && new=$old && new_name="$old_name"
+
+# If we don't have an old 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
+
+[ -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"
+
+if [ "$force" ]
+then
+    git-read-tree --reset $new &&
+       git-checkout-index -q -f -u -a
+else
+    git-update-index --refresh >/dev/null
+    merge_error=$(git-read-tree -m -u $old $new 2>&1) || (
+       case "$merge" in
+       '')
+               echo >&2 "$merge_error"
+               exit 1 ;;
+       esac
+
+       # 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 $new &&
+       git checkout-index -f -u -q -a &&
+       git read-tree -m -u --aggressive $old $new $work || 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
+
+       # Do not register the cleanly merged paths in the index yet.
+       # this is not a real merge before committing, but just carrying
+       # the working tree changes along.
+       unmerged=`git ls-files -u`
+       git read-tree --reset $new
+       case "$unmerged" in
+       '')     ;;
+       *)
+               (
+                       z40=0000000000000000000000000000000000000000
+                       echo "$unmerged" |
+                       sed -e 's/^[0-7]* [0-9a-f]* /'"0 $z40 /"
+                       echo "$unmerged"
+               ) | git update-index --index-info
+               ;;
+       esac
+       exit 0
+    )
+    saved_err=$?
+    if test "$saved_err" = 0
+    then
+       test "$new" = "$old" || git diff-index --name-status "$new"
+    fi
+    (exit $saved_err)
+fi
+
+# 
+# Switch the HEAD pointer to the new branch if we
+# checked out a branch head, and remove any potential
+# old MERGE_HEAD's (subsequent commits will clearly not
+# be based on them, since we re-set the index)
+#
+if [ "$?" -eq 0 ]; then
+       if [ "$newbranch" ]; then
+               if [ "$newbranch_log" ]; then
+                       mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch")
+                       touch "$GIT_DIR/logs/refs/heads/$newbranch"
+               fi
+               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"
+       rm -f "$GIT_DIR/MERGE_HEAD"
+else
+       exit 1
+fi
diff --git a/git-cherry.sh b/git-cherry.sh
new file mode 100755 (executable)
index 0000000..f0e8831
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+USAGE='[-v] <upstream> [<head>] [<limit>]'
+LONG_USAGE='             __*__*__*__*__> <upstream>
+            /
+  fork-point
+            \__+__+__+__+__+__+__+__> <head>
+
+Each commit between the fork-point (or <limit> if given) and <head> is
+examined, and compared against the change each commit between the
+fork-point and <upstream> introduces.  If the change seems to be in
+the upstream, it is shown on the standard output with prefix "+".
+Otherwise it is shown with prefix "-".'
+. git-sh-setup
+
+case "$1" in -v) verbose=t; shift ;; esac 
+
+case "$#,$1" in
+1,*..*)
+    upstream=$(expr "z$1" : 'z\(.*\)\.\.') ours=$(expr "z$1" : '.*\.\.\(.*\)$')
+    set x "$upstream" "$ours"
+    shift ;;
+esac
+
+case "$#" in
+1) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify HEAD` || exit
+   limit="$upstream"
+   ;;
+2) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify "$2"` || exit
+   limit="$upstream"
+   ;;
+3) upstream=`git-rev-parse --verify "$1"` &&
+   ours=`git-rev-parse --verify "$2"` &&
+   limit=`git-rev-parse --verify "$3"` || exit
+   ;;
+*) usage ;;
+esac
+
+# Note that these list commits in reverse order;
+# not that the order in inup matters...
+inup=`git-rev-list ^$ours $upstream` &&
+ours=`git-rev-list $ours ^$limit` || exit
+
+tmp=.cherry-tmp$$
+patch=$tmp-patch
+mkdir $patch
+trap "rm -rf $tmp-*" 0 1 2 3 15
+
+_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"
+
+for c in $inup
+do
+       git-diff-tree -p $c
+done | git-patch-id |
+while read id name
+do
+       echo $name >>$patch/$id
+done
+
+LF='
+'
+
+O=
+for c in $ours
+do
+       set x `git-diff-tree -p $c | git-patch-id`
+       if test "$2" != ""
+       then
+               if test -f "$patch/$2"
+               then
+                       sign=-
+               else
+                       sign=+
+               fi
+               case "$verbose" in
+               t)
+                       c=$(git-rev-list --pretty=oneline --max-count=1 $c)
+               esac
+               case "$O" in
+               '')     O="$sign $c" ;;
+               *)      O="$sign $c$LF$O" ;;
+               esac
+       fi
+done
+case "$O" in
+'') ;;
+*)  echo "$O" ;;
+esac
diff --git a/git-clean.sh b/git-clean.sh
new file mode 100755 (executable)
index 0000000..3834323
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# Copyright (c) 2005-2006 Pavel Roskin
+#
+
+USAGE="[-d] [-n] [-q] [-x | -X] [--] <paths>..."
+LONG_USAGE='Clean untracked files from the working directory
+       -d      remove directories as well
+       -n      don'\''t remove anything, just show what would be done
+       -q      be quiet, only report errors
+       -x      remove ignored files as well
+       -X      remove only ignored files
+When optional <paths>... arguments are given, the paths
+affected are further limited to those that match them.'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+
+ignored=
+ignoredonly=
+cleandir=
+quiet=
+rmf="rm -f --"
+rmrf="rm -rf --"
+rm_refuse="echo Not removing"
+echo1="echo"
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -d)
+               cleandir=1
+               ;;
+       -n)
+               quiet=1
+               rmf="echo Would remove"
+               rmrf="echo Would remove"
+               rm_refuse="echo Would not remove"
+               echo1=":"
+               ;;
+       -q)
+               quiet=1
+               ;;
+       -x)
+               ignored=1
+               ;;
+       -X)
+               ignoredonly=1
+               ;;
+       --)
+               shift
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+       esac
+       shift
+done
+
+case "$ignored,$ignoredonly" in
+       1,1) usage;;
+esac
+
+if [ -z "$ignored" ]; then
+       excl="--exclude-per-directory=.gitignore"
+       if [ -f "$GIT_DIR/info/exclude" ]; then
+               excl_info="--exclude-from=$GIT_DIR/info/exclude"
+       fi
+       if [ "$ignoredonly" ]; then
+               excl="$excl --ignored"
+       fi
+fi
+
+git-ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
+while read -r file; do
+       if [ -d "$file" -a ! -L "$file" ]; then
+               if [ -z "$cleandir" ]; then
+                       $rm_refuse "$file"
+                       continue
+               fi
+               $echo1 "Removing $file"
+               $rmrf "$file"
+       else
+               $echo1 "Removing $file"
+               $rmf "$file"
+       fi
+done
diff --git a/git-clone.sh b/git-clone.sh
new file mode 100755 (executable)
index 0000000..de59904
--- /dev/null
@@ -0,0 +1,417 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, Linus Torvalds
+# Copyright (c) 2005, Junio C Hamano
+# 
+# Clone a repository into a different directory that does not yet exist.
+
+# See git-sh-setup why.
+unset CDPATH
+
+usage() {
+       echo >&2 "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+       exit 1
+}
+
+get_repo_base() {
+       (cd "$1" && (cd .git ; pwd)) 2> /dev/null
+}
+
+if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+    curl_extra_args="-k"
+fi
+
+http_fetch () {
+       # $1 = Remote, $2 = Local
+       curl -nsfL $curl_extra_args "$1" >"$2"
+}
+
+clone_dumb_http () {
+       # $1 - remote, $2 - local
+       cd "$2" &&
+       clone_tmp='.git/clone-tmp' &&
+       mkdir -p "$clone_tmp" || exit 1
+       http_fetch "$1/info/refs" "$clone_tmp/refs" || {
+               echo >&2 "Cannot get remote repository information.
+Perhaps git-update-server-info needs to be run there?"
+               exit 1;
+       }
+       while read sha1 refname
+       do
+               name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
+               case "$name" in
+               *^*)    continue;;
+               esac
+               if test -n "$use_separate_remote" &&
+                  branch_name=`expr "z$name" : 'zheads/\(.*\)'`
+               then
+                       tname="remotes/$origin/$branch_name"
+               else
+                       tname=$name
+               fi
+               git-http-fetch -v -a -w "$tname" "$name" "$1/" || exit 1
+       done <"$clone_tmp/refs"
+       rm -fr "$clone_tmp"
+       http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
+       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
+local_shared=no
+unset template
+no_checkout=
+upload_pack=
+bare=
+reference=
+origin=
+origin_override=
+use_separate_remote=
+while
+       case "$#,$1" in
+       0,*) break ;;
+       *,-n|*,--no|*,--no-|*,--no-c|*,--no-ch|*,--no-che|*,--no-chec|\
+       *,--no-check|*,--no-checko|*,--no-checkou|*,--no-checkout)
+         no_checkout=yes ;;
+       *,--na|*,--nak|*,--nake|*,--naked|\
+       *,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;;
+       *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
+        *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) 
+          local_shared=yes; use_local=yes ;;
+       1,--template) usage ;;
+       *,--template)
+               shift; template="--template=$1" ;;
+       *,--template=*)
+         template="$1" ;;
+       *,-q|*,--quiet) quiet=-q ;;
+       *,--use-separate-remote)
+               use_separate_remote=t ;;
+       1,--reference) usage ;;
+       *,--reference)
+               shift; reference="$1" ;;
+       *,--reference=*)
+               reference=`expr "$1" : '--reference=\(.*\)'` ;;
+       *,-o|*,--or|*,--ori|*,--orig|*,--origi|*,--origin)
+               case "$2" in
+               '')
+                   usage ;;
+               */*)
+                   echo >&2 "'$2' is not suitable for an origin name"
+                   exit 1
+               esac
+               git-check-ref-format "heads/$2" || {
+                   echo >&2 "'$2' is not suitable for a branch name"
+                   exit 1
+               }
+               test -z "$origin_override" || {
+                   echo >&2 "Do not give more than one --origin options."
+                   exit 1
+               }
+               origin_override=yes
+               origin="$2"; shift
+               ;;
+       1,-u|1,--upload-pack) usage ;;
+       *,-u|*,--upload-pack)
+               shift
+               upload_pack="--exec=$1" ;;
+       *,-*) usage ;;
+       *) break ;;
+       esac
+do
+       shift
+done
+
+repo="$1"
+if test -z "$repo"
+then
+    echo >&2 'you must specify a repository to clone.'
+    exit 1
+fi
+
+# --bare implies --no-checkout
+if test yes = "$bare"
+then
+       if test yes = "$origin_override"
+       then
+               echo >&2 '--bare and --origin $origin options are incompatible.'
+               exit 1
+       fi
+       if test t = "$use_separate_remote"
+       then
+               echo >&2 '--bare and --use-separate-remote options are incompatible.'
+               exit 1
+       fi
+       no_checkout=yes
+fi
+
+if test -z "$origin"
+then
+       origin=origin
+fi
+
+# Turn the source into an absolute path if
+# it is local
+if base=$(get_repo_base "$repo"); then
+       repo="$base"
+       local=yes
+fi
+
+dir="$2"
+# Try using "humanish" part of source repo if user didn't specify one
+[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+[ -e "$dir" ] && echo "$dir already exists." && usage
+mkdir -p "$dir" &&
+D=$(cd "$dir" && pwd) &&
+trap 'err=$?; cd ..; rm -r "$D"; exit $err' 0
+case "$bare" in
+yes) GIT_DIR="$D" ;;
+*) GIT_DIR="$D/.git" ;;
+esac && export GIT_DIR && git-init-db ${template+"$template"} || usage
+case "$bare" in
+yes)
+       GIT_DIR="$D" ;;
+*)
+       GIT_DIR="$D/.git" ;;
+esac
+
+if test -n "$reference"
+then
+       if test -d "$reference"
+       then
+               if test -d "$reference/.git/objects"
+               then
+                       reference="$reference/.git"
+               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 -)
+       else
+               echo >&2 "$reference: not a local directory." && usage
+       fi
+fi
+
+rm -f "$GIT_DIR/CLONE_HEAD"
+
+# We do local magic only when the user tells us to.
+case "$local,$use_local" in
+yes,yes)
+       ( cd "$repo/objects" ) || {
+               echo >&2 "-l flag seen but $repo is not local."
+               exit 1
+       }
+
+       case "$local_shared" in
+       no)
+           # See if we can hardlink and drop "l" if not.
+           sample_file=$(cd "$repo" && \
+                         find objects -type f -print | sed -e 1q)
+
+           # objects directory should not be empty since we are cloning!
+           test -f "$repo/$sample_file" || exit
+
+           l=
+           if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+           then
+                   l=l
+           fi &&
+           rm -f "$GIT_DIR/objects/sample" &&
+           cd "$repo" &&
+           find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
+           ;;
+       yes)
+           mkdir -p "$GIT_DIR/objects/info"
+           echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
+           ;;
+       esac
+       git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+       ;;
+*)
+       case "$repo" in
+       rsync://*)
+               rsync $quiet -av --ignore-existing  \
+                       --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
+               exit
+               # Look at objects/info/alternates for rsync -- http will
+               # support it natively and git native ones will do it on the
+               # remote end.  Not having that file is not a crime.
+               rsync -q "$repo/objects/info/alternates" \
+                       "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+                       rm -f "$GIT_DIR/TMP_ALT"
+               if test -f "$GIT_DIR/TMP_ALT"
+               then
+                   ( cd "$D" &&
+                     . git-parse-remote &&
+                     resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
+                   while read alt
+                   do
+                       case "$alt" in 'bad alternate: '*) die "$alt";; esac
+                       case "$quiet" in
+                       '')     echo >&2 "Getting alternate: $alt" ;;
+                       esac
+                       rsync $quiet -av --ignore-existing  \
+                           --exclude info "$alt" "$GIT_DIR/objects" || exit
+                   done
+                   rm -f "$GIT_DIR/TMP_ALT"
+               fi
+               git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
+               ;;
+       http://*)
+               if test -z "@@NO_CURL@@"
+               then
+                       clone_dumb_http "$repo" "$D"
+               else
+                       echo >&2 "http transport not supported, rebuild Git with curl support"
+                       exit 1
+               fi
+               ;;
+       *)
+               cd "$D" && case "$upload_pack" in
+               '') git-fetch-pack --all -k $quiet "$repo" ;;
+               *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
+               esac >"$GIT_DIR/CLONE_HEAD" || {
+                       echo >&2 "fetch-pack from '$repo' failed."
+                       exit 1
+               }
+               ;;
+       esac
+       ;;
+esac
+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"
+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
+
+       head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+       case "$head_sha1" in
+       'ref: refs/'*)
+               # Uh-oh, the remote told us (http transport done against
+               # new style repository with a symref HEAD).
+               # Ideally we should skip the guesswork but for now
+               # opt for minimum change.
+               head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
+               head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
+               ;;
+       esac
+
+       # The name under $remote_top the remote HEAD seems to point at.
+       head_points_at=$(
+               (
+                       echo "master"
+                       cd "$GIT_DIR/$remote_top" &&
+                       find . -type f -print | sed -e 's/^\.\///'
+               ) | (
+               done=f
+               while read name
+               do
+                       test t = $done && continue
+                       branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
+                       if test "$head_sha1" = "$branch_tip"
+                       then
+                               echo "$name"
+                               done=t
+                       fi
+               done
+               )
+       )
+
+       # Write out remotes/$origin file, and update our "$head_points_at".
+       case "$head_points_at" in
+       ?*)
+               mkdir -p "$GIT_DIR/remotes" &&
+               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" : './\(.*\)'` &&
+                       test "$use_separate_remote" = '' && {
+                               test "$head_points_at" = "$name" ||
+                               test "$origin" = "$name"
+                       } ||
+                       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
+       esac
+
+       case "$no_checkout" in
+       '')
+               git-read-tree -m -u -v HEAD HEAD
+       esac
+fi
+rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
+
+trap - 0
+
diff --git a/git-commit.sh b/git-commit.sh
new file mode 100755 (executable)
index 0000000..6dd04fd
--- /dev/null
@@ -0,0 +1,740 @@
+#!/bin/sh
+#
+# 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>...]'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+
+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)
+       status_only=t
+       unmerged_ok_if_status=--unmerged ;;
+*commit)
+       status_only=
+       unmerged_ok_if_status= ;;
+esac
+
+refuse_partial () {
+       echo >&2 "$1"
+       echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
+       exit 1
+}
+
+THIS_INDEX="$GIT_DIR/index"
+NEXT_INDEX="$GIT_DIR/next-index$$"
+rm -f "$NEXT_INDEX"
+save_index () {
+       cp "$THIS_INDEX" "$NEXT_INDEX"
+}
+
+report () {
+  header="#
+# $1:
+#   ($2)
+#
+"
+  trailer=""
+  while read status name newname
+  do
+    printf '%s' "$header"
+    header=""
+    trailer="#
+"
+    case "$status" in
+    M ) echo "#        modified: $name";;
+    D*) echo "#        deleted:  $name";;
+    T ) echo "#        typechange: $name";;
+    C*) echo "#        copied: $name -> $newname";;
+    R*) echo "#        renamed: $name -> $newname";;
+    A*) echo "#        new file: $name";;
+    U ) echo "#        unmerged: $name";;
+    esac
+  done
+  printf '%s' "$trailer"
+  [ "$header" ]
+}
+
+run_status () {
+    (
+       # We always show status for the whole tree.
+       cd "$TOP"
+
+       IS_INITIAL="$initial_commit"
+       REFERENCE=HEAD
+       case "$amend" in
+       t)
+               # If we are amending the initial commit, there
+               # is no HEAD^1.
+               if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1
+               then
+                       REFERENCE="HEAD^1"
+                       IS_INITIAL=
+               else
+                       IS_INITIAL=t
+               fi
+               ;;
+       esac
+
+       # If TMP_INDEX is defined, that means we are doing
+       # "--only" partial commit, and that index file is used
+       # to build the tree for the commit.  Otherwise, if
+       # NEXT_INDEX exists, that is the index file used to
+       # make the commit.  Otherwise we are using as-is commit
+       # so the regular index file is what we use to compare.
+       if test '' != "$TMP_INDEX"
+       then
+           GIT_INDEX_FILE="$TMP_INDEX"
+           export GIT_INDEX_FILE
+       elif test -f "$NEXT_INDEX"
+       then
+           GIT_INDEX_FILE="$NEXT_INDEX"
+           export GIT_INDEX_FILE
+       fi
+
+       case "$branch" in
+       refs/heads/master) ;;
+       *)  echo "# On branch $branch" ;;
+       esac
+
+       if test -z "$IS_INITIAL"
+       then
+           git-diff-index -M --cached --name-status \
+               --diff-filter=MDTCRA $REFERENCE |
+           sed -e '
+                   s/\\/\\\\/g
+                   s/ /\\ /g
+           ' |
+           report "Updated but not checked in" "will commit"
+           committable="$?"
+       else
+           echo '#
+# Initial commit
+#'
+           git-ls-files |
+           sed -e '
+                   s/\\/\\\\/g
+                   s/ /\\ /g
+                   s/^/A /
+           ' |
+           report "Updated but not checked in" "will commit"
+
+           committable="$?"
+       fi
+
+       git-diff-files  --name-status |
+       sed -e '
+               s/\\/\\\\/g
+               s/ /\\ /g
+       ' |
+       report "Changed but not updated" \
+           "use git-update-index to mark for commit"
+
+        option=""
+        if test -z "$untracked_files"; then
+            option="--directory --no-empty-directory"
+        fi
+       if test -f "$GIT_DIR/info/exclude"
+       then
+           git-ls-files -z --others $option \
+               --exclude-from="$GIT_DIR/info/exclude" \
+               --exclude-per-directory=.gitignore
+       else
+           git-ls-files -z --others $option \
+               --exclude-per-directory=.gitignore
+       fi |
+       perl -e '$/ = "\0";
+           my $shown = 0;
+           while (<>) {
+               chomp;
+               s|\\|\\\\|g;
+               s|\t|\\t|g;
+               s|\n|\\n|g;
+               s/^/#   /;
+               if (!$shown) {
+                   print "#\n# Untracked files:\n";
+                   print "#   (use \"git add\" to add to commit)\n";
+                   print "#\n";
+                   $shown = 1;
+               }
+               print "$_\n";
+           }
+       '
+
+       if test -n "$verbose" -a -z "$IS_INITIAL"
+       then
+           git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE
+       fi
+       case "$committable" in
+       0)
+               case "$amend" in
+               t)
+                       echo "# No changes" ;;
+               *)
+                       echo "nothing to commit" ;;
+               esac
+               exit 1 ;;
+       esac
+       exit 0
+    )
+}
+
+trap '
+       test -z "$TMP_INDEX" || {
+               test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
+       }
+       rm -f "$NEXT_INDEX"
+' 0
+
+################################################################
+# Command line argument parsing and sanity checking
+
+all=
+also=
+only=
+logfile=
+use_commit=
+amend=
+no_edit=
+log_given=
+log_message=
+verify=t
+verbose=
+signoff=
+force_author=
+only_include_assumed=
+untracked_files=
+while case "$#" in 0) break;; esac
+do
+  case "$1" in
+  -F|--F|-f|--f|--fi|--fil|--file)
+      case "$#" in 1) usage ;; esac
+      shift
+      no_edit=t
+      log_given=t$log_given
+      logfile="$1"
+      shift
+      ;;
+  -F*|-f*)
+      no_edit=t
+      log_given=t$log_given
+      logfile=`expr "$1" : '-[Ff]\(.*\)'`
+      shift
+      ;;
+  --F=*|--f=*|--fi=*|--fil=*|--file=*)
+      no_edit=t
+      log_given=t$log_given
+      logfile=`expr "$1" : '-[^=]*=\(.*\)'`
+      shift
+      ;;
+  -a|--a|--al|--all)
+      all=t
+      shift
+      ;;
+  --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+      force_author=`expr "$1" : '-[^=]*=\(.*\)'`
+      shift
+      ;;
+  --au|--aut|--auth|--autho|--author)
+      case "$#" in 1) usage ;; esac
+      shift
+      force_author="$1"
+      shift
+      ;;
+  -e|--e|--ed|--edi|--edit)
+      no_edit=
+      shift
+      ;;
+  -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+      also=t
+      shift
+      ;;
+  -o|--o|--on|--onl|--only)
+      only=t
+      shift
+      ;;
+  -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message="$1"
+      else
+          log_message="$log_message
+
+$1"
+      fi
+      no_edit=t
+      shift
+      ;;
+  -m*)
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "$1" : '-m\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "$1" : '-m\(.*\)'`"
+      fi
+      no_edit=t
+      shift
+      ;;
+  --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+      log_given=m$log_given
+      if test "$log_message" = ''
+      then
+          log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+      else
+          log_message="$log_message
+
+`expr "$1" : '-[^=]*=\(.*\)'`"
+      fi
+      no_edit=t
+      shift
+      ;;
+  -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify)
+      verify=
+      shift
+      ;;
+  --a|--am|--ame|--amen|--amend)
+      amend=t
+      log_given=t$log_given
+      use_commit=HEAD
+      shift
+      ;;
+  -c)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=t$log_given
+      use_commit="$1"
+      no_edit=
+      shift
+      ;;
+  --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+  --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+  --reedit-messag=*|--reedit-message=*)
+      log_given=t$log_given
+      use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+      no_edit=
+      shift
+      ;;
+  --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+  --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=t$log_given
+      use_commit="$1"
+      no_edit=
+      shift
+      ;;
+  -C)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=t$log_given
+      use_commit="$1"
+      no_edit=t
+      shift
+      ;;
+  --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+  --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+  --reuse-message=*)
+      log_given=t$log_given
+      use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+      no_edit=t
+      shift
+      ;;
+  --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+  --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+      case "$#" in 1) usage ;; esac
+      shift
+      log_given=t$log_given
+      use_commit="$1"
+      no_edit=t
+      shift
+      ;;
+  -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+      signoff=t
+      shift
+      ;;
+  -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+      verbose=t
+      shift
+      ;;
+  -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\
+  --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\
+  --untracked-files)
+      untracked_files=t
+      shift
+      ;;
+  --)
+      shift
+      break
+      ;;
+  -*)
+      usage
+      ;;
+  *)
+      break
+      ;;
+  esac
+done
+
+################################################################
+# Sanity check options
+
+case "$amend,$initial_commit" in
+t,t)
+  die "You do not have anything to amend." ;;
+t,)
+  if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+    die "You are in the middle of a merge -- cannot amend."
+  fi ;;
+esac
+
+case "$log_given" in
+tt*)
+  die "Only one of -c/-C/-F can be used." ;;
+*tm*|*mt*)
+  die "Option -m cannot be combined with -c/-C/-F." ;;
+esac
+
+case "$#,$also,$only,$amend" in
+*,t,t,*)
+  die "Only one of --include/--only can be used." ;;
+0,t,,* | 0,,t,)
+  die "No paths with --include/--only does not make sense." ;;
+0,,t,t)
+  only_include_assumed="# Clever... amending the last one with dirty index." ;;
+0,,,*)
+  ;;
+*,,,*)
+  only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+  also=
+  ;;
+esac
+unset only
+case "$all,$also,$#" in
+t,t,*)
+       die "Cannot use -a and -i at the same time." ;;
+t,,[1-9]*)
+       die "Paths with -a does not make sense." ;;
+,t,0)
+       die "No paths with -i does not make sense." ;;
+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
+               git-diff-files --name-only -z |
+               git-update-index --remove -z --stdin
+       )
+       ;;
+,t)
+       save_index &&
+       git-ls-files --error-unmatch -- "$@" >/dev/null || exit
+
+       git-diff-files --name-only -z -- "$@"  |
+       (
+               cd "$TOP"
+               GIT_INDEX_FILE="$NEXT_INDEX"
+               export GIT_INDEX_FILE
+               git-update-index --remove -z --stdin
+       )
+       ;;
+,)
+       case "$#" in
+       0)
+           ;; # commit as-is
+       *)
+           if test -f "$GIT_DIR/MERGE_HEAD"
+           then
+               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
+           # the same way.
+           if test -z "$initial_commit"
+           then
+               cp "$THIS_INDEX" "$TMP_INDEX"
+               GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD
+           else
+                   rm -f "$TMP_INDEX"
+           fi || exit
+
+           echo "$commit_only" |
+           GIT_INDEX_FILE="$TMP_INDEX" \
+           git-update-index --add --remove --stdin &&
+
+           save_index &&
+           echo "$commit_only" |
+           (
+               GIT_INDEX_FILE="$NEXT_INDEX"
+               export GIT_INDEX_FILE
+               git-update-index --remove --stdin
+           ) || exit
+           ;;
+       esac
+       ;;
+esac
+
+################################################################
+# If we do as-is commit, the index file will be THIS_INDEX,
+# otherwise NEXT_INDEX after we make this commit.  We leave
+# the index as is if we abort.
+
+if test -f "$NEXT_INDEX"
+then
+       USE_INDEX="$NEXT_INDEX"
+else
+       USE_INDEX="$THIS_INDEX"
+fi
+
+GIT_INDEX_FILE="$USE_INDEX" \
+    git-update-index -q $unmerged_ok_if_status --refresh || exit
+
+################################################################
+# If the request is status, just show it and exit.
+
+case "$0" in
+*status)
+       run_status
+       exit $?
+esac
+
+################################################################
+# Grab commit message, write out tree and make commit.
+
+if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
+then
+       if test "$TMP_INDEX"
+       then
+               GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
+       else
+               GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
+       fi || exit
+fi
+
+if test "$log_message" != ''
+then
+       echo "$log_message"
+elif test "$logfile" != ""
+then
+       if test "$logfile" = -
+       then
+               test -t 0 &&
+               echo >&2 "(reading log message from standard input)"
+               cat
+       else
+               cat <"$logfile"
+       fi
+elif test "$use_commit" != ""
+then
+       git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
+elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+then
+       cat "$GIT_DIR/MERGE_MSG"
+fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+
+case "$signoff" in
+t)
+       {
+               echo
+               git-var GIT_COMMITTER_IDENT | sed -e '
+                       s/>.*/>/
+                       s/^/Signed-off-by: /
+               '
+       } >>"$GIT_DIR"/COMMIT_EDITMSG
+       ;;
+esac
+
+if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
+       echo "#"
+       echo "# It looks like you may be committing a MERGE."
+       echo "# If this is not correct, please remove the file"
+       echo "# $GIT_DIR/MERGE_HEAD"
+       echo "# and try again"
+       echo "#"
+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 "malformatted --author parameter"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+elif test '' != "$use_commit"
+then
+       pick_author_script='
+       /^author /{
+               s/'\''/'\''\\'\'\''/g
+               h
+               s/^author \([^<]*\) <[^>]*> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+               g
+               s/^author [^<]* <\([^>]*\)> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+               g
+               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+               q
+       }
+       '
+       set_author_env=`git-cat-file commit "$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
+
+PARENTS="-p HEAD"
+if test -z "$initial_commit"
+then
+       if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+               PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+       elif test -n "$amend"; then
+               PARENTS=$(git-cat-file commit HEAD |
+                       sed -n -e '/^$/q' -e 's/^parent /-p /p')
+       fi
+       current=$(git-rev-parse --verify HEAD)
+else
+       if [ -z "$(git-ls-files)" ]; then
+               echo >&2 Nothing to commit
+               exit 1
+       fi
+       PARENTS=""
+       current=
+fi
+
+if test -z "$no_edit"
+then
+       {
+               echo ""
+               echo "# Please enter the commit message for your changes."
+               echo "# (Comment lines starting with '#' will not be included)"
+               test -z "$only_include_assumed" || echo "$only_include_assumed"
+               run_status
+       } >>"$GIT_DIR"/COMMIT_EDITMSG
+else
+       # we need to check if there is anything to commit
+       run_status >/dev/null 
+fi
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
+then
+       rm -f "$GIT_DIR/COMMIT_EDITMSG"
+       run_status
+       exit 1
+fi
+
+case "$no_edit" in
+'')
+       case "${VISUAL:-$EDITOR},$TERM" in
+       ,dumb)
+               echo >&2 "Terminal is dumb but no VISUAL nor EDITOR defined."
+               echo >&2 "Please supply the commit log message using either"
+               echo >&2 "-m or -F option.  A boilerplate log message has"
+               echo >&2 "been prepared in $GIT_DIR/COMMIT_EDITMSG"
+               exit 1
+               ;;
+       esac
+       git-var GIT_AUTHOR_IDENT > /dev/null  || die
+       git-var GIT_COMMITTER_IDENT > /dev/null  || die
+       ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
+       ;;
+esac
+
+case "$verify" in
+t)
+       if test -x "$GIT_DIR"/hooks/commit-msg
+       then
+               "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
+       fi
+esac
+
+sed -e '
+    /^diff --git a\/.*/{
+       s///
+       q
+    }
+    /^#/d
+' "$GIT_DIR"/COMMIT_EDITMSG |
+git-stripspace >"$GIT_DIR"/COMMIT_MSG
+
+if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+       git-stripspace |
+       wc -l` &&
+   test 0 -lt $cnt
+then
+       if test -z "$TMP_INDEX"
+       then
+               tree=$(GIT_INDEX_FILE="$USE_INDEX" git-write-tree)
+       else
+               tree=$(GIT_INDEX_FILE="$TMP_INDEX" git-write-tree) &&
+               rm -f "$TMP_INDEX"
+       fi &&
+       commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
+       rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+       git-update-ref -m "commit: $rlogm" HEAD $commit $current &&
+       rm -f -- "$GIT_DIR/MERGE_HEAD" &&
+       if test -f "$NEXT_INDEX"
+       then
+               mv "$NEXT_INDEX" "$THIS_INDEX"
+       else
+               : ;# happy
+       fi
+else
+       echo >&2 "* no commit message?  aborting commit."
+       false
+fi
+ret="$?"
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"
+if test -d "$GIT_DIR/rr-cache"
+then
+       git-rerere
+fi
+
+if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
+then
+       "$GIT_DIR"/hooks/post-commit
+fi
+exit "$ret"
diff --git a/git-compat-util.h b/git-compat-util.h
new file mode 100644 (file)
index 0000000..5d543d2
--- /dev/null
@@ -0,0 +1,159 @@
+#ifndef GIT_COMPAT_UTIL_H
+#define GIT_COMPAT_UTIL_H
+
+#ifndef FLEX_ARRAY
+#if defined(__GNUC__) && (__GNUC__ < 3)
+#define FLEX_ARRAY 0
+#else
+#define FLEX_ARRAY /* empty */
+#endif
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#ifdef __GNUC__
+#define NORETURN __attribute__((__noreturn__))
+#else
+#define NORETURN
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+/* General helper functions */
+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)));
+
+#ifdef NO_MMAP
+
+#ifndef PROT_READ
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define MAP_PRIVATE 1
+#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);
+
+#else /* NO_MMAP */
+
+#include <sys/mman.h>
+
+#endif /* NO_MMAP */
+
+#ifdef NO_SETENV
+#define setenv gitsetenv
+extern int gitsetenv(const char *, const char *, int);
+#endif
+
+#ifdef NO_UNSETENV
+#define unsetenv gitunsetenv
+extern void gitunsetenv(const char *);
+#endif
+
+#ifdef NO_STRCASESTR
+#define strcasestr gitstrcasestr
+extern char *gitstrcasestr(const char *haystack, const char *needle);
+#endif
+
+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");
+       return ret;
+}
+
+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");
+       return ret;
+}
+
+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");
+       return ret;
+}
+
+static inline ssize_t xread(int fd, void *buf, size_t len)
+{
+       ssize_t nr;
+       while (1) {
+               nr = read(fd, buf, len);
+               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+                       continue;
+               return nr;
+       }
+}
+
+static inline ssize_t xwrite(int fd, const void *buf, size_t len)
+{
+       ssize_t nr;
+       while (1) {
+               nr = write(fd, buf, len);
+               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+                       continue;
+               return nr;
+       }
+}
+
+/* Sane ctype - no locale, and works with signed chars */
+#undef isspace
+#undef isdigit
+#undef isalpha
+#undef isalnum
+#undef tolower
+#undef toupper
+extern unsigned char sane_ctype[256];
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isspace(x) sane_istest(x,GIT_SPACE)
+#define isdigit(x) sane_istest(x,GIT_DIGIT)
+#define isalpha(x) sane_istest(x,GIT_ALPHA)
+#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define tolower(x) sane_case((unsigned char)(x), 0x20)
+#define toupper(x) sane_case((unsigned char)(x), 0)
+
+static inline int sane_case(int x, int high)
+{
+       if (sane_istest(x, GIT_ALPHA))
+               x = (x & ~0x20) | high;
+       return x;
+}
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 256
+#endif
+#endif
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
new file mode 100755 (executable)
index 0000000..57088c3
--- /dev/null
@@ -0,0 +1,288 @@
+#!/usr/bin/perl -w
+
+# Known limitations:
+# - cannot add or remove binary files
+# - does not propagate permissions
+# - tells "ready for commit" even when things could not be completed
+#   (eg addition of a binary file)
+
+use strict;
+use Getopt::Std;
+use File::Temp qw(tempdir);
+use Data::Dumper;
+use File::Basename qw(basename dirname);
+
+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_m );
+
+getopts('hpvcfm:');
+
+$opt_h && usage();
+
+die "Need at least one commit identifier!" unless @ARGV;
+
+# setup a tempdir
+our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',
+                                    TMPDIR => 1,
+                                    CLEANUP => 1);
+
+print Dumper(@ARGV);
+# resolve target commit
+my $commit;
+$commit = pop @ARGV;
+$commit = safe_pipe_capture('git-rev-parse', '--verify', "$commit^0");
+chomp $commit;
+if ($?) {
+    die "The commit reference $commit did not resolve!";
+}
+
+# resolve what parent we want
+my $parent;
+if (@ARGV) {
+    $parent = pop @ARGV;
+    $parent =  safe_pipe_capture('git-rev-parse', '--verify', "$parent^0");
+    chomp $parent;
+    if ($?) {
+       die "The parent reference did not resolve!";
+    }
+}
+
+# find parents from the commit itself
+my @commit  = safe_pipe_capture('git-cat-file', 'commit', $commit);
+my @parents;
+foreach my $p (@commit) {
+    if ($p =~ m/^$/) { # end of commit headers, we're done
+       last;
+    }
+    if ($p =~ m/^parent (\w{40})$/) { # found a parent
+       push @parents, $1;
+    }
+}
+
+if ($parent) {
+    # double check that it's a valid parent
+    foreach my $p (@parents) {
+       my $found;
+       if ($p eq $parent) {
+           $found = 1;
+           last;
+       }; # found it
+       die "Did not find $parent in the parents for this commit!";
+    }
+} 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];
+    } else { # or perhaps not!
+       die "This commit has more than one parent -- please name the parent you want to use explicitly";
+    }
+}
+
+$opt_v && print "Applying to CVS commit $commit from parent $parent\n";
+
+# grab the commit message
+open(MSG, ">.msg") or die "Cannot open .msg for writing";
+print MSG $opt_m;
+close MSG;
+
+`git-cat-file commit $commit | sed -e '1,/^\$/d' >> .msg`;
+$? && die "Error extracting the commit message";
+
+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 'R') {
+       push @dfiles, $fields[5];
+    }
+}
+$opt_v && print "The commit affects:\n ";
+$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
+undef @files; # don't need it anymore
+
+# check that the files are clean and up to date according to cvs
+my $dirty;
+foreach my $d (@dirs) {
+    if (-e $d) {
+       $dirty = 1;
+       warn "$d exists and is not a directory!\n";
+    }
+}
+foreach my $f (@afiles) {
+    # This should return only one value
+    my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));
+    if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
+    if (-d dirname $f and $status[0] !~ m/Status: Unknown$/
+       and $status[0] !~ m/^File: no file /) {
+       $dirty = 1;
+       warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
+       warn "Status was: $status\n";
+    }
+}
+foreach my $f (@mfiles, @dfiles) {
+    # 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?'};
+    unless ($status[0] =~ m/Status: Up-to-date$/) {
+       $dirty = 1;
+       warn "File $f not up to date in your CVS checkout!\n";
+    }
+}
+if ($dirty) {
+    if ($opt_f) {      warn "The tree is not clean -- forced merge\n";
+       $dirty = 0;
+    } else {
+       die "Exiting: your CVS tree is not clean for this merge.";
+    }
+}
+
+###
+### NOTE: if you are planning to die() past this point
+###       you MUST call cleanupcvs(@files) before die()
+###
+
+
+print "Creating new directories\n";
+foreach my $d (@dirs) {
+    unless (mkdir $d) {
+        warn "Could not mkdir $d: $!";
+       $dirty = 1;
+    }
+    `cvs add $d`;
+    if ($?) {
+       $dirty = 1;
+       warn "Failed to cvs add directory $d -- you may need to do it manually";
+    }
+}
+
+print "'Patching' binary files\n";
+
+my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit));
+@bfiles = map { chomp } @bfiles;
+foreach my $f (@bfiles) {
+    # check that the file in cvs matches the "old" file
+    # extract the file to $tmpdir and comparre with cmp
+    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";
+       $dirty = 1;
+       next;
+    }
+
+    # 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";
+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) {
+    system('cvs', 'add', $f);
+    if ($?) {
+       $dirty = 1;
+       warn "Failed to cvs add $f -- you may need to do it manually";
+    }
+}
+
+foreach my $f (@dfiles) {
+    system('cvs', 'rm', '-f', $f);
+    if ($?) {
+       $dirty = 1;
+       warn "Failed to cvs rm -f $f -- you may need to do it manually";
+    }
+}
+
+print "Commit to CVS\n";
+my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
+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 "\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);
+    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";
+}
+sub usage {
+       print STDERR <<END;
+Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
+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 alterative 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
+sub safe_pipe_capture {
+    my @output;
+    if (my $pid = open my $child, '-|') {
+       @output = (<$child>);
+       close $child or die join(' ',@_).": $! $?";
+    } else {
+       exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+    }
+    return wantarray ? @output : join('',@output);
+}
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
new file mode 100755 (executable)
index 0000000..76f6246
--- /dev/null
@@ -0,0 +1,880 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to aggregate CVS check-ins into related changes.
+# Fortunately, "cvsps" does that for us; all we have to do is to parse
+# its output.
+#
+# Checking out the files is done by a single long-running CVS connection
+# / server process.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Socket;
+use IO::Pipe;
+use POSIX qw(strftime dup2 ENOENT);
+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);
+my (%conv_author_name, %conv_author_email);
+
+sub usage() {
+       print STDERR <<END;
+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]
+END
+       exit(1);
+}
+
+sub read_author_info($) {
+       my ($file) = @_;
+       my $user;
+       open my $f, '<', "$file" or die("Failed to open $file: $!\n");
+
+       while (<$f>) {
+               # Expected format is this:
+               #   exon=Andreas Ericsson <ae@op5.se>
+               if (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/) {
+                       $user = $1;
+                       $conv_author_name{$user} = $2;
+                       $conv_author_email{$user} = $3;
+               }
+               # However, we also read from CVSROOT/users format
+               # to ease migration.
+               elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) {
+                       my $mapped;
+                       ($user, $mapped) = ($1, $3);
+                       if ($mapped =~ /^\s*(.*?)\s*<(.*)>\s*$/) {
+                               $conv_author_name{$user} = $1;
+                               $conv_author_email{$user} = $2;
+                       }
+                       elsif ($mapped =~ /^<?(.*)>?$/) {
+                               $conv_author_name{$user} = $user;
+                               $conv_author_email{$user} = $1;
+                       }
+               }
+               # NEEDSWORK: Maybe warn on unrecognized lines?
+       }
+       close ($f);
+}
+
+sub write_author_info($) {
+       my ($file) = @_;
+       open my $f, '>', $file or
+         die("Failed to open $file for writing: $!");
+
+       foreach (keys %conv_author_name) {
+               print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>\n";
+       }
+       close ($f);
+}
+
+getopts("hivmkuo:d:p:C:z:s:M:P:A:S:L:") or usage();
+usage if $opt_h;
+
+@ARGV <= 1 or usage();
+
+if($opt_d) {
+       $ENV{"CVSROOT"} = $opt_d;
+} 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"}) {
+       $opt_d = $ENV{"CVSROOT"};
+} else {
+       die "CVSROOT needs to be set";
+}
+$opt_o ||= "origin";
+$opt_s ||= "-";
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $cvs_tree;
+if ($#ARGV == 0) {
+       $cvs_tree = $ARGV[0];
+} elsif (-f 'CVS/Repository') {
+       open my $f, '<', 'CVS/Repository' or 
+           die 'Failed to open CVS/Repository';
+       $cvs_tree = <$f>;
+       chomp $cvs_tree;
+       close $f;
+} else {
+       usage();
+}
+
+our @mergerx = ();
+if ($opt_m) {
+       @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+}
+if ($opt_M) {
+       push (@mergerx, qr/$opt_M/);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package CVSconn;
+# Basic CVS dialog.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+       my($what,$repo,$subdir) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       $self->{'subdir'} = $subdir;
+       $self->{'lines'} = undef;
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+               my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+               $user="anonymous" unless defined $user;
+               my $rr2 = "-";
+               unless($port) {
+                       $rr2 = ":pserver:$user\@$serv:$repo";
+                       $port=2401;
+               }
+               my $rr = ":pserver:$user\@$serv:$port$repo";
+
+               unless($pass) {
+                       open(H,$ENV{'HOME'}."/.cvspass") and do {
+                               # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+                               while(<H>) {
+                                       chomp;
+                                       s/^\/\d+\s+//;
+                                       my ($w,$p) = split(/\s/,$_,2);
+                                       if($w eq $rr or $w eq $rr2) {
+                                               $pass = $p;
+                                               last;
+                                       }
+                               }
+                       };
+               }
+               $pass="A" unless $pass;
+
+               my $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>;
+
+               if($rep ne "I LOVE YOU\n") {
+                       $rep="<unknown>" unless $rep;
+                       die "AuthReply: $rep\n";
+               }
+               $self->{'socketo'} = $s;
+               $self->{'socketi'} = $s;
+       } else { # local or ext: Fork off our own cvs server.
+               my $pr = IO::Pipe->new();
+               my $pw = IO::Pipe->new();
+               my $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               my $cvs = 'cvs';
+               $cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
+               my $rsh = 'rsh';
+               $rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
+
+               my @cvs = ($cvs, 'server');
+               my ($local, $user, $host);
+               $local = $repo =~ s/:local://;
+               if (!$local) {
+                   $repo =~ s/:ext://;
+                   $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
+                   ($user, $host) = ($1, $2);
+               }
+               if (!$local) {
+                   if ($user) {
+                       unshift @cvs, $rsh, '-l', $user, $host;
+                   } else {
+                       unshift @cvs, $rsh, $host;
+                   }
+               }
+
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+                       exec(@cvs);
+               }
+               $pw->writer();
+               $pr->reader();
+               $self->{'socketo'} = $pw;
+               $self->{'socketi'} = $pr;
+       }
+       $self->{'socketo'}->write("Root $repo\n");
+
+       # Trial and error says that this probably is the minimum set
+       $self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
+
+       $self->{'socketo'}->write("valid-requests\n");
+       $self->{'socketo'}->flush();
+
+       chomp(my $rep=$self->readline());
+       if($rep !~ s/^Valid-requests\s*//) {
+               $rep="<unknown>" unless $rep;
+               die "Expected Valid-requests from server, but got: $rep\n";
+       }
+       chomp(my $res=$self->readline());
+       die "validReply: $res\n" if $res ne "ok";
+
+       $self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
+       $self->{'repo'} = $repo;
+}
+
+sub readline {
+       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) = @_;
+       $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
+       if ($opt_k) {
+           $self->{'socketo'}->write("Argument -kk\n") or return undef;
+       }
+       $self->{'socketo'}->write("Argument -r\n") or return undef;
+       $self->{'socketo'}->write("Argument $rev\n") or return undef;
+       $self->{'socketo'}->write("Argument --\n") or return undef;
+       $self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
+       $self->{'socketo'}->write("Directory .\n") or return undef;
+       $self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
+       # $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
+       $self->{'socketo'}->write("co\n") or return undef;
+       $self->{'socketo'}->flush() or return undef;
+       $self->{'lines'} = 0;
+       return 1;
+}
+sub _line {
+       # Read a line from the server.
+       # ... except that 'line' may be an entire file. ;-)
+       my($self, $fh) = @_;
+       die "Not in lines" unless defined $self->{'lines'};
+
+       my $line;
+       my $res=0;
+       while(defined($line = $self->readline())) {
+               # M U gnupg-cvs-rep/AUTHORS
+               # Updated gnupg-cvs-rep/
+               # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
+               # /AUTHORS/1.1///T1.1
+               # u=rw,g=rw,o=rw
+               # 0
+               # ok
+
+               if($line =~ s/^(?:Created|Updated) //) {
+                       $line = $self->readline(); # path
+                       $line = $self->readline(); # Entries line
+                       my $mode = $self->readline(); chomp $mode;
+                       $self->{'mode'} = $mode;
+                       defined (my $cnt = $self->readline())
+                               or die "EOF from server after 'Changed'\n";
+                       chomp $cnt;
+                       die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
+                       $line="";
+                       $res = $self->_fetchfile($fh, $cnt);
+               } elsif($line =~ s/^ //) {
+                       print $fh $line;
+                       $res += length($line);
+               } elsif($line =~ /^M\b/) {
+                       # output, do nothing
+               } elsif($line =~ /^Mbinary\b/) {
+                       my $cnt;
+                       die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
+                       chomp $cnt;
+                       die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
+                       $line="";
+                       $res += $self->_fetchfile($fh, $cnt);
+               } else {
+                       chomp $line;
+                       if($line eq "ok") {
+                               # print STDERR "S: ok (".length($res).")\n";
+                               return $res;
+                       } elsif($line =~ s/^E //) {
+                               # print STDERR "S: $line\n";
+                       } elsif($line =~ /^(Remove-entry|Removed) /i) {
+                               $line = $self->readline(); # filename
+                               $line = $self->readline(); # OK
+                               chomp $line;
+                               die "Unknown: $line" if $line ne "ok";
+                               return -1;
+                       } else {
+                               die "Unknown: $line\n";
+                       }
+               }
+       }
+       return undef;
+}
+sub file {
+       my($self,$fn,$rev) = @_;
+       my $res;
+
+       my ($fh, $name) = tempfile('gitcvs.XXXXXX', 
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       $self->_file($fn,$rev) and $res = $self->_line($fh);
+
+       if (!defined $res) {
+           print STDERR "Server has gone away while fetching $fn $rev, retrying...\n";
+           truncate $fh, 0;
+           $self->conn();
+           $self->_file($fn,$rev) or die "No file command send";
+           $res = $self->_line($fh);
+           die "Retry failed" unless defined $res;
+       }
+       close ($fh);
+
+       return ($name, $res);
+}
+sub _fetchfile {
+       my ($self, $fh, $cnt) = @_;
+       my $res = 0;
+       my $bufsize = 1024 * 1024;
+       while($cnt) {
+           if ($bufsize > $cnt) {
+               $bufsize = $cnt;
+           }
+           my $buf;
+           my $num = $self->{'socketi'}->read($buf,$bufsize);
+           die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+           print $fh $buf;
+           $res += $num;
+           $cnt -= $num;
+       }
+       return $res;
+}
+
+
+package main;
+
+my $cvs = CVSconn->new($opt_d, $cvs_tree);
+
+
+sub pdate($) {
+       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;
+       return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub pmode($) {
+       my($mode) = @_;
+       my $m = 0;
+       my $mm = 0;
+       my $um = 0;
+       for my $x(split(//,$mode)) {
+               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
+               } else { die "Unknown mode: $mode\n";
+               }
+       }
+       $m |= $mm&$um;
+       return $m;
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+sub is_sha1 {
+       my $s = shift;
+       return $s =~ /^[a-f0-9]{40}$/;
+}
+
+sub get_headref ($$) {
+    my $name    = shift;
+    my $git_dir = shift; 
+    
+    my $f = "$git_dir/refs/heads/$name";
+    if(open(my $fh, $f)) {
+           chomp(my $r = <$fh>);
+           is_sha1($r) or die "Cannot get head id for $name ($r): $!";
+           return $r;
+    }
+    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
+    return undef;
+}
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $last_branch = "";
+my $orig_branch = "";
+my %branch_date;
+my $tip_at_start = undef;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+                                   DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+unless(-d $git_dir) {
+       system("git-init-db");
+       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+       system("git-read-tree");
+       die "Cannot init an empty tree: $?\n" if $?;
+
+       $last_branch = $opt_o;
+       $orig_branch = "";
+} else {
+       -f "$git_dir/refs/heads/$opt_o"
+               or die "Branch '$opt_o' does not exist.\n".
+                      "Either use the correct '-o branch' option,\n".
+                      "or import to a new repository.\n";
+
+       open(F, "git-symbolic-ref HEAD |") or
+               die "Cannot run git-symbolic-ref: $!\n";
+       chomp ($last_branch = <F>);
+       $last_branch = basename($last_branch);
+       close(F);
+       unless($last_branch) {
+               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+               $last_branch = "master";
+       }
+       $orig_branch = $last_branch;
+       $tip_at_start = `git-rev-parse --verify HEAD`;
+
+       # populate index
+       system('git-read-tree', $last_branch);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       opendir(D,"$git_dir/refs/heads");
+       while(defined(my $head = readdir(D))) {
+               next if $head =~ /^\./;
+               open(F,"$git_dir/refs/heads/$head")
+                       or die "Bad head branch: $head: $!\n";
+               chomp(my $ftag = <F>);
+               close(F);
+               open(F,"git-cat-file commit $ftag |");
+               while(<F>) {
+                       next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
+                       $branch_date{$head} = $1;
+                       last;
+               }
+               close(F);
+       }
+       closedir(D);
+}
+
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+# now we read (and possibly save) author-info as well
+-f "$git_dir/cvs-authors" and
+  read_author_info("$git_dir/cvs-authors");
+if ($opt_A) {
+       read_author_info($opt_A);
+       write_author_info("$git_dir/cvs-authors");
+}
+
+my $pid = open(CVS,"-|");
+die "Cannot fork: $!\n" unless defined $pid;
+unless($pid) {
+       my @opt;
+       @opt = split(/,/,$opt_p) if defined $opt_p;
+       unshift @opt, '-z', $opt_z if defined $opt_z;
+       unshift @opt, '-q'         unless defined $opt_v;
+       unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
+               push @opt, '--cvs-direct';
+       }
+       if ($opt_P) {
+           exec("cat", $opt_P);
+       } else {
+           exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+           die "Could not start cvsps: $!\n";
+       }
+}
+
+
+## cvsps output:
+#---------------------
+#PatchSet 314
+#Date: 1999/09/18 13:03:59
+#Author: wkoch
+#Branch: STABLE-BRANCH-1-0
+#Ancestor branch: HEAD
+#Tag: (none)
+#Log:
+#    See ChangeLog: Sat Sep 18 13:03:28 CEST 1999  Werner Koch
+#Members:
+#      README:1.57->1.57.2.1
+#      VERSION:1.96->1.96.2.1
+#
+#---------------------
+
+my $state = 0;
+
+sub update_index (\@\@) {
+       my $old = shift;
+       my $new = shift;
+       open(my $fh, '|-', qw(git-update-index -z --index-info))
+               or die "unable to open git-update-index: $!";
+       print $fh
+               (map { "0 0000000000000000000000000000000000000000\t$_\0" }
+                       @$old),
+               (map { '100' . sprintf('%o', $_->[0]) . " $_->[1]\t$_->[2]\0" }
+                       @$new)
+               or die "unable to write to git-update-index: $!";
+       close $fh
+               or die "unable to write to git-update-index: $!";
+       $? and die "git-update-index reported error: $?";
+}
+
+sub write_tree () {
+       open(my $fh, '-|', qw(git-write-tree))
+               or die "unable to open git-write-tree: $!";
+       chomp(my $tree = <$fh>);
+       is_sha1($tree)
+               or die "Cannot get tree id ($tree): $!";
+       close($fh)
+               or die "Error running git-write-tree: $?\n";
+       print "Tree ID $tree\n" if $opt_v;
+       return $tree;
+}
+
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new,@skipped);
+sub commit {
+       update_index(@old, @new);
+       @old = @new = ();
+       my $tree = write_tree();
+       my $parent = get_headref($last_branch, $git_dir);
+       print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
+
+       my @commit_args;
+       push @commit_args, ("-p", $parent) if $parent;
+
+       # loose detection of merges
+       # based on the commit msg
+       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)) {
+                       push @commit_args, '-p', $mparent;
+                       print "Merge parent branch: $mparent\n" if $opt_v;
+               }
+       }
+
+       my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date));
+       $ENV{GIT_AUTHOR_NAME} = $author_name;
+       $ENV{GIT_AUTHOR_EMAIL} = $author_email;
+       $ENV{GIT_AUTHOR_DATE} = $commit_date;
+       $ENV{GIT_COMMITTER_NAME} = $author_name;
+       $ENV{GIT_COMMITTER_EMAIL} = $author_email;
+       $ENV{GIT_COMMITTER_DATE} = $commit_date;
+       my $pid = open2(my $commit_read, my $commit_write,
+               'git-commit-tree', $tree, @commit_args);
+
+       # compatibility with git2cvs
+       substr($logmsg,32767) = "" if length($logmsg) > 32767;
+       $logmsg =~ s/[\s\n]+\z//;
+
+       if (@skipped) {
+           $logmsg .= "\n\n\nSKIPPED:\n\t";
+           $logmsg .= join("\n\t", @skipped) . "\n";
+           @skipped = ();
+       }
+
+       print($commit_write "$logmsg\n") && close($commit_write)
+               or die "Error writing to git-commit-tree: $!\n";
+
+       print "Committed patch $patchset ($branch $commit_date)\n" if $opt_v;
+       chomp(my $cid = <$commit_read>);
+       is_sha1($cid) or die "Cannot get commit id ($cid): $!\n";
+       print "Commit ID $cid\n" if $opt_v;
+       close($commit_read);
+
+       waitpid($pid,0);
+       die "Error running git-commit-tree: $?\n" if $?;
+
+       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;
+               $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
+               $xtag =~ tr/_/\./ if ( $opt_u );
+               $xtag =~ s/[\/]/$opt_s/g;
+               
+               my $pid = open2($in, $out, 'git-mktag');
+               print $out "object $cid\n".
+                   "type commit\n".
+                   "tag $xtag\n".
+                   "tagger $author_name <$author_email>\n"
+                   or die "Cannot create tag object $xtag: $!\n";
+               close($out)
+                   or die "Cannot create tag object $xtag: $!\n";
+
+               my $tagobj = <$in>;
+               chomp $tagobj;
+
+               if ( !close($in) or waitpid($pid, 0) != $pid or
+                    $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+                   die "Cannot create tag object $xtag: $!\n";
+               }
+               
+
+               open(C,">$git_dir/refs/tags/$xtag")
+                       or die "Cannot create tag $xtag: $!\n";
+               print C "$tagobj\n"
+                       or die "Cannot write tag $xtag: $!\n";
+               close(C)
+                       or die "Cannot write tag $xtag: $!\n";
+
+               print "Created tag '$xtag' on '$branch'\n" if $opt_v;
+       }
+};
+
+my $commitcount = 1;
+while(<CVS>) {
+       chomp;
+       if($state == 0 and /^-+$/) {
+               $state = 1;
+       } elsif($state == 0) {
+               $state = 1;
+               redo;
+       } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+               $patchset = 0+$_;
+               $state=2;
+       } elsif($state == 2 and s/^Date:\s+//) {
+               $date = pdate($_);
+               unless($date) {
+                       print STDERR "Could not parse date: $_\n";
+                       $state=0;
+                       next;
+               }
+               $state=3;
+       } elsif($state == 3 and s/^Author:\s+//) {
+               s/\s+$//;
+               if (/^(.*?)\s+<(.*)>/) {
+                   ($author_name, $author_email) = ($1, $2);
+               } elsif ($conv_author_name{$_}) {
+                       $author_name = $conv_author_name{$_};
+                       $author_email = $conv_author_email{$_};
+               } else {
+                   $author_name = $author_email = $_;
+               }
+               $state = 4;
+       } elsif($state == 4 and s/^Branch:\s+//) {
+               s/\s+$//;
+               s/[\/]/$opt_s/g;
+               $branch = $_;
+               $state = 5;
+       } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+               s/\s+$//;
+               $ancestor = $_;
+               $ancestor = $opt_o if $ancestor eq "HEAD";
+               $state = 6;
+       } elsif($state == 5) {
+               $ancestor = undef;
+               $state = 6;
+               redo;
+       } elsif($state == 6 and s/^Tag:\s+//) {
+               s/\s+$//;
+               if($_ eq "(none)") {
+                       $tag = undef;
+               } else {
+                       $tag = $_;
+               }
+               $state = 7;
+       } elsif($state == 7 and /^Log:/) {
+               $logmsg = "";
+               $state = 8;
+       } elsif($state == 8 and /^Members:/) {
+               $branch = $opt_o if $branch eq "HEAD";
+               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($ancestor) {
+                       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")) {
+                               print STDERR "Branch $ancestor does not exist!\n";
+                               $state=11;
+                               next;
+                       }
+                       chomp(my $id = <H>);
+                       close(H);
+                       unless(open(H,"> $git_dir/refs/heads/$branch")) {
+                               print STDERR "Could not create branch $branch: $!\n";
+                               $state=11;
+                               next;
+                       }
+                       print H "$id\n"
+                               or die "Could not write branch $branch: $!";
+                       close(H)
+                               or die "Could not write branch $branch: $!";
+               }
+               if(($ancestor || $branch) ne $last_branch) {
+                       print "Switching from $last_branch to $branch\n" if $opt_v;
+                       system("git-read-tree", $branch);
+                       die "read-tree failed: $?\n" if $?;
+               }
+               $last_branch = $branch if $branch ne $last_branch;
+               $state = 9;
+       } elsif($state == 8) {
+               $logmsg .= "$_\n";
+       } 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;
+               my $rev = $3;
+               $fn =~ s#^/+##;
+               if ($opt_S && $fn =~ m/$opt_S/) {
+                   print "SKIPPING $fn v $rev\n";
+                   push(@skipped, $fn);
+                   next;
+               }
+               print "Fetching $fn   v $rev\n" if $opt_v;
+               my ($tmpname, $size) = $cvs->file($fn,$rev);
+               if($size == -1) {
+                       push(@old,$fn);
+                       print "Drop $fn\n" if $opt_v;
+               } else {
+                       print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
+                       my $pid = open(my $F, '-|');
+                       die $! unless defined $pid;
+                       if (!$pid) {
+                           exec("git-hash-object", "-w", $tmpname)
+                               or die "Cannot create object: $!\n";
+                       }
+                       my $sha = <$F>;
+                       chomp $sha;
+                       close $F;
+                       my $mode = pmode($cvs->{'mode'});
+                       push(@new,[$mode, $sha, $fn]); # may be resurrected!
+               }
+               unlink($tmpname);
+       } 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*$/) {
+               $state = 10;
+       } elsif(($state == 9 or $state == 10) and /^-+$/) {
+               $commitcount++;
+               if ($opt_L && $commitcount > $opt_L) {
+                       last;
+               }
+               commit();
+               if (($commitcount & 1023) == 0) {
+                       system("git repack -a -d");
+               }
+               $state = 1;
+       } elsif($state == 11 and /^-+$/) {
+               $state = 1;
+       } elsif(/^-+$/) { # end of unknown-line processing
+               $state = 1;
+       } elsif($state != 11) { # ignore stuff when skipping
+               print "* UNKNOWN LINE * $_\n";
+       }
+}
+commit() if $branch and $state != 11;
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+       $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+       delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+       print "DONE.\n" if $opt_v;
+       if ($opt_i) {
+               exit 0;
+       }
+       my $tip_at_end = `git-rev-parse --verify HEAD`;
+       if ($tip_at_start ne $tip_at_end) {
+               for ($tip_at_start, $tip_at_end) { chomp; }
+               print "Fetched into the current branch.\n" if $opt_v;
+               system(qw(git-read-tree -u -m),
+                      $tip_at_start, $tip_at_end);
+               die "Fast-forward update failed: $?\n" if $?;
+       }
+       else {
+               system(qw(git-merge cvsimport HEAD), "refs/heads/$opt_o");
+               die "Could not merge $opt_o into the current branch.\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v;
+       system("git-update-ref", "refs/heads/master", "refs/heads/$opt_o")
+               unless -f "$git_dir/refs/heads/master";
+       system('git-update-ref', 'HEAD', "$orig_branch");
+       unless ($opt_i) {
+               system('git checkout');
+               die "checkout failed: $?\n" if $?;
+       }
+}
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
new file mode 100755 (executable)
index 0000000..5ccca4f
--- /dev/null
@@ -0,0 +1,2692 @@
+#!/usr/bin/perl
+
+####
+#### This application is a CVS emulation layer for git.
+#### It is intended for clients to connect over SSH.
+#### See the documentation for more details.
+####
+#### Copyright The Open University UK - 2006.
+####
+#### Authors: Martyn Smith    <martyn@catalyst.net.nz>
+####          Martin Langhoff <martin@catalyst.net.nz>
+####
+####
+#### Released under the GNU Public License, version 2.
+####
+####
+
+use strict;
+use warnings;
+
+use Fcntl;
+use File::Temp qw/tempdir tempfile/;
+use File::Basename;
+
+my $log = GITCVS::log->new();
+my $cfg;
+
+my $DATE_LIST = {
+    Jan => "01",
+    Feb => "02",
+    Mar => "03",
+    Apr => "04",
+    May => "05",
+    Jun => "06",
+    Jul => "07",
+    Aug => "08",
+    Sep => "09",
+    Oct => "10",
+    Nov => "11",
+    Dec => "12",
+};
+
+# Enable autoflush for STDOUT (otherwise the whole thing falls apart)
+$| = 1;
+
+#### Definition and mappings of functions ####
+
+my $methods = {
+    'Root'            => \&req_Root,
+    'Valid-responses' => \&req_Validresponses,
+    'valid-requests'  => \&req_validrequests,
+    'Directory'       => \&req_Directory,
+    'Entry'           => \&req_Entry,
+    'Modified'        => \&req_Modified,
+    'Unchanged'       => \&req_Unchanged,
+    'Questionable'    => \&req_Questionable,
+    'Argument'        => \&req_Argument,
+    'Argumentx'       => \&req_Argument,
+    'expand-modules'  => \&req_expandmodules,
+    'add'             => \&req_add,
+    'remove'          => \&req_remove,
+    'co'              => \&req_co,
+    'update'          => \&req_update,
+    'ci'              => \&req_ci,
+    'diff'            => \&req_diff,
+    'log'             => \&req_log,
+    'rlog'            => \&req_log,
+    'tag'             => \&req_CATCHALL,
+    'status'          => \&req_status,
+    'admin'           => \&req_CATCHALL,
+    'history'         => \&req_CATCHALL,
+    'watchers'        => \&req_CATCHALL,
+    'editors'         => \&req_CATCHALL,
+    'annotate'        => \&req_annotate,
+    'Global_option'   => \&req_Globaloption,
+    #'annotate'        => \&req_CATCHALL,
+};
+
+##############################################
+
+
+# $state holds all the bits of information the clients sends us that could
+# potentially be useful when it comes to actually _doing_ something.
+my $state = {};
+$log->info("--------------- STARTING -----------------");
+
+my $TEMP_DIR = tempdir( CLEANUP => 1 );
+$log->debug("Temporary directory is '$TEMP_DIR'");
+
+# if we are called with a pserver argument,
+# deal with the authentication cat before entering the
+# main loop
+if (@ARGV && $ARGV[0] eq 'pserver') {
+    my $line = <STDIN>; chomp $line;
+    unless( $line eq 'BEGIN AUTH REQUEST') {
+       die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
+    }
+    $line = <STDIN>; chomp $line;
+    req_Root('root', $line) # reuse Root
+       or die "E Invalid root $line \n";
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'anonymous') {
+       print "E Only anonymous user allowed via pserver\n";
+       print "I HATE YOU\n";
+    }
+    $line = <STDIN>; chomp $line;    # validate the password?
+    $line = <STDIN>; chomp $line;
+    unless ($line eq 'END AUTH REQUEST') {
+       die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+    }
+    print "I LOVE YOU\n";
+    # and now back to our regular programme...
+}
+
+# Keep going until the client closes the connection
+while (<STDIN>)
+{
+    chomp;
+
+    # Check to see if we've seen this method, and call appropriate function.
+    if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
+    {
+        # use the $methods hash to call the appropriate sub for this command
+        #$log->info("Method : $1");
+        &{$methods->{$1}}($1,$2);
+    } else {
+        # log fatal because we don't understand this function. If this happens
+        # we're fairly screwed because we don't know if the client is expecting
+        # a response. If it is, the client will hang, we'll hang, and the whole
+        # thing will be custard.
+        $log->fatal("Don't understand command $_\n");
+        die("Unknown command $_");
+    }
+}
+
+$log->debug("Processing time : user=" . (times)[0] . " system=" . (times)[1]);
+$log->info("--------------- FINISH -----------------");
+
+# Magic catchall method.
+#    This is the method that will handle all commands we haven't yet
+#    implemented. It simply sends a warning to the log file indicating a
+#    command that hasn't been implemented has been invoked.
+sub req_CATCHALL
+{
+    my ( $cmd, $data ) = @_;
+    $log->warn("Unhandled command : req_$cmd : $data");
+}
+
+
+# Root pathname \n
+#     Response expected: no. Tell the server which CVSROOT to use. Note that
+#     pathname is a local directory and not a fully qualified CVSROOT variable.
+#     pathname must already exist; if creating a new root, use the init
+#     request, not Root. pathname does not include the hostname of the server,
+#     how to access the server, etc.; by the time the CVS protocol is in use,
+#     connection, authentication, etc., are already taken care of. The Root
+#     request must be sent only once, and it must be sent before any requests
+#     other than Valid-responses, valid-requests, UseUnchanged, Set or init.
+sub req_Root
+{
+    my ( $cmd, $data ) = @_;
+    $log->debug("req_Root : $data");
+
+    $state->{CVSROOT} = $data;
+
+    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
+       print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+        print "E \n";
+        print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+       return 0;
+    }
+
+    my @gitvars = `git-repo-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 \n";
+        print "error 1 - problem executing git-repo-config\n";
+       return 0;
+    }
+    foreach my $line ( @gitvars )
+    {
+        next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
+        $cfg->{$1}{$2} = $3;
+    }
+
+    unless ( defined ( $cfg->{gitcvs}{enabled} ) and $cfg->{gitcvs}{enabled} =~ /^\s*(1|true|yes)\s*$/i )
+    {
+        print "E GITCVS emulation needs to be enabled on this repo\n";
+        print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
+        print "E \n";
+        print "error 1 GITCVS emulation disabled\n";
+        return 0;
+    }
+
+    if ( defined ( $cfg->{gitcvs}{logfile} ) )
+    {
+        $log->setfile($cfg->{gitcvs}{logfile});
+    } else {
+        $log->nofile();
+    }
+
+    return 1;
+}
+
+# Global_option option \n
+#     Response expected: no. Transmit one of the global options `-q', `-Q',
+#     `-l', `-t', `-r', or `-n'. option must be one of those strings, no
+#     variations (such as combining of options) are allowed. For graceful
+#     handling of valid-requests, it is probably better to make new global
+#     options separate requests, rather than trying to add them to this
+#     request.
+sub req_Globaloption
+{
+    my ( $cmd, $data ) = @_;
+    $log->debug("req_Globaloption : $data");
+    $state->{globaloptions}{$data} = 1;
+}
+
+# Valid-responses request-list \n
+#     Response expected: no. Tell the server what responses the client will
+#     accept. request-list is a space separated list of tokens.
+sub req_Validresponses
+{
+    my ( $cmd, $data ) = @_;
+    $log->debug("req_Validresponses : $data");
+
+    # TODO : re-enable this, currently it's not particularly useful
+    #$state->{validresponses} = [ split /\s+/, $data ];
+}
+
+# valid-requests \n
+#     Response expected: yes. Ask the server to send back a Valid-requests
+#     response.
+sub req_validrequests
+{
+    my ( $cmd, $data ) = @_;
+
+    $log->debug("req_validrequests");
+
+    $log->debug("SEND : Valid-requests " . join(" ",keys %$methods));
+    $log->debug("SEND : ok");
+
+    print "Valid-requests " . join(" ",keys %$methods) . "\n";
+    print "ok\n";
+}
+
+# Directory local-directory \n
+#     Additional data: repository \n. Response expected: no. Tell the server
+#     what directory to use. The repository should be a directory name from a
+#     previous server response. Note that this both gives a default for Entry
+#     and Modified and also for ci and the other commands; normal usage is to
+#     send Directory for each directory in which there will be an Entry or
+#     Modified, and then a final Directory for the original directory, then the
+#     command. The local-directory is relative to the top level at which the
+#     command is occurring (i.e. the last Directory which is sent before the
+#     command); to indicate that top level, `.' should be sent for
+#     local-directory.
+sub req_Directory
+{
+    my ( $cmd, $data ) = @_;
+
+    my $repository = <STDIN>;
+    chomp $repository;
+
+
+    $state->{localdir} = $data;
+    $state->{repository} = $repository;
+    $state->{path} = $repository;
+    $state->{path} =~ s/^$state->{CVSROOT}\///;
+    $state->{module} = $1 if ($state->{path} =~ s/^(.*?)(\/|$)//);
+    $state->{path} .= "/" if ( $state->{path} =~ /\S/ );
+
+    $state->{directory} = $state->{localdir};
+    $state->{directory} = "" if ( $state->{directory} eq "." );
+    $state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
+
+    if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+    {
+        $log->info("Setting prepend to '$state->{path}'");
+        $state->{prependdir} = $state->{path};
+        foreach my $entry ( keys %{$state->{entries}} )
+        {
+            $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
+            delete $state->{entries}{$entry};
+        }
+    }
+
+    if ( defined ( $state->{prependdir} ) )
+    {
+        $log->debug("Prepending '$state->{prependdir}' to state|directory");
+        $state->{directory} = $state->{prependdir} . $state->{directory}
+    }
+    $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
+}
+
+# Entry entry-line \n
+#     Response expected: no. Tell the server what version of a file is on the
+#     local machine. The name in entry-line is a name relative to the directory
+#     most recently specified with Directory. If the user is operating on only
+#     some files in a directory, Entry requests for only those files need be
+#     included. If an Entry request is sent without Modified, Is-modified, or
+#     Unchanged, it means the file is lost (does not exist in the working
+#     directory). If both Entry and one of Modified, Is-modified, or Unchanged
+#     are sent for the same file, Entry must be sent first. For a given file,
+#     one can send Modified, Is-modified, or Unchanged, but not more than one
+#     of these three.
+sub req_Entry
+{
+    my ( $cmd, $data ) = @_;
+
+    #$log->debug("req_Entry : $data");
+
+    my @data = split(/\//, $data);
+
+    $state->{entries}{$state->{directory}.$data[1]} = {
+        revision    => $data[2],
+        conflict    => $data[3],
+        options     => $data[4],
+        tag_or_date => $data[5],
+    };
+
+    $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
+}
+
+# Questionable filename \n
+#     Response expected: no. Additional data: no. Tell the server to check
+#     whether filename should be ignored, and if not, next time the server
+#     sends responses, send (in a M response) `?' followed by the directory and
+#     filename. filename must not contain `/'; it needs to be a file in the
+#     directory named by the most recent Directory request.
+sub req_Questionable
+{
+    my ( $cmd, $data ) = @_;
+
+    $log->debug("req_Questionable : $data");
+    $state->{entries}{$state->{directory}.$data}{questionable} = 1;
+}
+
+# add \n
+#     Response expected: yes. Add a file or directory. This uses any previous
+#     Argument, Directory, Entry, or Modified requests, if they have been sent.
+#     The last Directory sent specifies the working directory at the time of
+#     the operation. To add a directory, send the directory to be added using
+#     Directory and Argument requests.
+sub req_add
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("add");
+
+    my $addcount = 0;
+
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        unless ( defined ( $state->{entries}{$filename}{modified_filename} ) )
+        {
+            print "E cvs add: nothing known about `$filename'\n";
+            next;
+        }
+        # TODO : check we're not squashing an already existing file
+        if ( defined ( $state->{entries}{$filename}{revision} ) )
+        {
+            print "E cvs add: `$filename' has already been entered\n";
+            next;
+        }
+
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
+
+        print "E cvs add: scheduling file `$filename' for addition\n";
+
+        print "Checked-in $dirpart\n";
+        print "$filename\n";
+        print "/$filepart/0///\n";
+
+        $addcount++;
+    }
+
+    if ( $addcount == 1 )
+    {
+        print "E cvs add: use `cvs commit' to add this file permanently\n";
+    }
+    elsif ( $addcount > 1 )
+    {
+        print "E cvs add: use `cvs commit' to add these files permanently\n";
+    }
+
+    print "ok\n";
+}
+
+# remove \n
+#     Response expected: yes. Remove a file. This uses any previous Argument,
+#     Directory, Entry, or Modified requests, if they have been sent. The last
+#     Directory sent specifies the working directory at the time of the
+#     operation. Note that this request does not actually do anything to the
+#     repository; the only effect of a successful remove request is to supply
+#     the client with a new entries line containing `-' to indicate a removed
+#     file. In fact, the client probably could perform this operation without
+#     contacting the server, although using remove may cause the server to
+#     perform a few more checks. The client sends a subsequent ci request to
+#     actually record the removal in the repository.
+sub req_remove
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("remove");
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    #$log->debug("add state : " . Dumper($state));
+
+    my $rmcount = 0;
+
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        if ( defined ( $state->{entries}{$filename}{unchanged} ) or defined ( $state->{entries}{$filename}{modified_filename} ) )
+        {
+            print "E cvs remove: file `$filename' still in working directory\n";
+            next;
+        }
+
+        my $meta = $updater->getmeta($filename);
+        my $wrev = revparse($filename);
+
+        unless ( defined ( $wrev ) )
+        {
+            print "E cvs remove: nothing known about `$filename'\n";
+            next;
+        }
+
+        if ( defined($wrev) and $wrev < 0 )
+        {
+            print "E cvs remove: file `$filename' already scheduled for removal\n";
+            next;
+        }
+
+        unless ( $wrev == $meta->{revision} )
+        {
+            # TODO : not sure if the format of this message is quite correct.
+            print "E cvs remove: Up to date check failed for `$filename'\n";
+            next;
+        }
+
+
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
+
+        print "E cvs remove: scheduling `$filename' for removal\n";
+
+        print "Checked-in $dirpart\n";
+        print "$filename\n";
+        print "/$filepart/-1.$wrev///\n";
+
+        $rmcount++;
+    }
+
+    if ( $rmcount == 1 )
+    {
+        print "E cvs remove: use `cvs commit' to remove this file permanently\n";
+    }
+    elsif ( $rmcount > 1 )
+    {
+        print "E cvs remove: use `cvs commit' to remove these files permanently\n";
+    }
+
+    print "ok\n";
+}
+
+# Modified filename \n
+#     Response expected: no. Additional data: mode, \n, file transmission. Send
+#     the server a copy of one locally modified file. filename is a file within
+#     the most recent directory sent with Directory; it must not contain `/'.
+#     If the user is operating on only some files in a directory, only those
+#     files need to be included. This can also be sent without Entry, if there
+#     is no entry for the file.
+sub req_Modified
+{
+    my ( $cmd, $data ) = @_;
+
+    my $mode = <STDIN>;
+    chomp $mode;
+    my $size = <STDIN>;
+    chomp $size;
+
+    # Grab config information
+    my $blocksize = 8192;
+    my $bytesleft = $size;
+    my $tmp;
+
+    # Get a filehandle/name to write it to
+    my ( $fh, $filename ) = tempfile( DIR => $TEMP_DIR );
+
+    # Loop over file data writing out to temporary file.
+    while ( $bytesleft )
+    {
+        $blocksize = $bytesleft if ( $bytesleft < $blocksize );
+        read STDIN, $tmp, $blocksize;
+        print $fh $tmp;
+        $bytesleft -= $blocksize;
+    }
+
+    close $fh;
+
+    # Ensure we have something sensible for the file mode
+    if ( $mode =~ /u=(\w+)/ )
+    {
+        $mode = $1;
+    } else {
+        $mode = "rw";
+    }
+
+    # Save the file data in $state
+    $state->{entries}{$state->{directory}.$data}{modified_filename} = $filename;
+    $state->{entries}{$state->{directory}.$data}{modified_mode} = $mode;
+    $state->{entries}{$state->{directory}.$data}{modified_hash} = `git-hash-object $filename`;
+    $state->{entries}{$state->{directory}.$data}{modified_hash} =~ s/\s.*$//s;
+
+    #$log->debug("req_Modified : file=$data mode=$mode size=$size");
+}
+
+# Unchanged filename \n
+#     Response expected: no. Tell the server that filename has not been
+#     modified in the checked out directory. The filename is a file within the
+#     most recent directory sent with Directory; it must not contain `/'.
+sub req_Unchanged
+{
+    my ( $cmd, $data ) = @_;
+
+    $state->{entries}{$state->{directory}.$data}{unchanged} = 1;
+
+    #$log->debug("req_Unchanged : $data");
+}
+
+# Argument text \n
+#     Response expected: no. Save argument for use in a subsequent command.
+#     Arguments accumulate until an argument-using command is given, at which
+#     point they are forgotten.
+# Argumentx text \n
+#     Response expected: no. Append \n followed by text to the current argument
+#     being saved.
+sub req_Argument
+{
+    my ( $cmd, $data ) = @_;
+
+    # TODO :  Not quite sure how Argument and Argumentx differ, but I assume
+    # it's for multi-line arguments ... somehow ...
+
+    $log->debug("$cmd : $data");
+
+    push @{$state->{arguments}}, $data;
+}
+
+# expand-modules \n
+#     Response expected: yes. Expand the modules which are specified in the
+#     arguments. Returns the data in Module-expansion responses. Note that the
+#     server can assume that this is checkout or export, not rtag or rdiff; the
+#     latter do not access the working directory and thus have no need to
+#     expand modules on the client side. Expand may not be the best word for
+#     what this request does. It does not necessarily tell you all the files
+#     contained in a module, for example. Basically it is a way of telling you
+#     which working directories the server needs to know about in order to
+#     handle a checkout of the specified modules. For example, suppose that the
+#     server has a module defined by
+#   aliasmodule -a 1dir
+#     That is, one can check out aliasmodule and it will take 1dir in the
+#     repository and check it out to 1dir in the working directory. Now suppose
+#     the client already has this module checked out and is planning on using
+#     the co request to update it. Without using expand-modules, the client
+#     would have two bad choices: it could either send information about all
+#     working directories under the current directory, which could be
+#     unnecessarily slow, or it could be ignorant of the fact that aliasmodule
+#     stands for 1dir, and neglect to send information for 1dir, which would
+#     lead to incorrect operation. With expand-modules, the client would first
+#     ask for the module to be expanded:
+sub req_expandmodules
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit();
+
+    $log->debug("req_expandmodules : " . ( defined($data) ? $data : "[NULL]" ) );
+
+    unless ( ref $state->{arguments} eq "ARRAY" )
+    {
+        print "ok\n";
+        return;
+    }
+
+    foreach my $module ( @{$state->{arguments}} )
+    {
+        $log->debug("SEND : Module-expansion $module");
+        print "Module-expansion $module\n";
+    }
+
+    print "ok\n";
+    statecleanup();
+}
+
+# co \n
+#     Response expected: yes. Get files from the repository. This uses any
+#     previous Argument, Directory, Entry, or Modified requests, if they have
+#     been sent. Arguments to this command are module names; the client cannot
+#     know what directories they correspond to except by (1) just sending the
+#     co request, and then seeing what directory names the server sends back in
+#     its responses, and (2) the expand-modules request.
+sub req_co
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("co");
+
+    my $module = $state->{args}[0];
+    my $checkout_path = $module;
+
+    # use the user specified directory if we're given it
+    $checkout_path = $state->{opt}{d} if ( exists ( $state->{opt}{d} ) );
+
+    $log->debug("req_co : " . ( defined($data) ? $data : "[NULL]" ) );
+
+    $log->info("Checking out module '$module' ($state->{CVSROOT}) to '$checkout_path'");
+
+    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log);
+    $updater->update();
+
+    $checkout_path =~ s|/$||; # get rid of trailing slashes
+
+    # Eclipse seems to need the Clear-sticky command
+    # to prepare the 'Entries' file for the new directory.
+    print "Clear-sticky $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Clear-static-directory $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Clear-sticky $checkout_path/\n"; # yes, twice
+    print $state->{CVSROOT} . "/$module/\n";
+    print "Template $checkout_path/\n";
+    print $state->{CVSROOT} . "/$module/\n";
+    print "0\n";
+
+    # instruct the client that we're checking out to $checkout_path
+    print "E cvs checkout: Updating $checkout_path\n";
+
+    my %seendirs = ();
+    my $lastdir ='';
+
+    # recursive
+    sub prepdir {
+       my ($dir, $repodir, $remotedir, $seendirs) = @_;
+       my $parent = dirname($dir);
+       $dir       =~ s|/+$||;
+       $repodir   =~ s|/+$||;
+       $remotedir =~ s|/+$||;
+       $parent    =~ s|/+$||;
+       $log->debug("announcedir $dir, $repodir, $remotedir" );
+
+       if ($parent eq '.' || $parent eq './') {
+           $parent = '';
+       }
+       # recurse to announce unseen parents first
+       if (length($parent) && !exists($seendirs->{$parent})) {
+           prepdir($parent, $repodir, $remotedir, $seendirs);
+       }
+       # Announce that we are going to modify at the parent level
+       if ($parent) {
+           print "E cvs checkout: Updating $remotedir/$parent\n";
+       } else {
+           print "E cvs checkout: Updating $remotedir\n";
+       }
+       print "Clear-sticky $remotedir/$parent/\n";
+       print "$repodir/$parent/\n";
+
+       print "Clear-static-directory $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
+       print "$repodir/$parent/\n";
+       print "Template $remotedir/$dir/\n";
+       print "$repodir/$dir/\n";
+       print "0\n";
+
+       $seendirs->{$dir} = 1;
+    }
+
+    foreach my $git ( @{$updater->gethead} )
+    {
+        # Don't want to check out deleted files
+        next if ( $git->{filehash} eq "deleted" );
+
+        ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
+
+       if (length($git->{dir}) && $git->{dir} ne './'
+           && $git->{dir} ne $lastdir ) {
+           unless (exists($seendirs{$git->{dir}})) {
+               prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
+                       $checkout_path, \%seendirs);
+               $lastdir = $git->{dir};
+               $seendirs{$git->{dir}} = 1;
+           }
+           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
+       }
+
+        # modification time of this file
+        print "Mod-time $git->{modified}\n";
+
+        # print some information to the client
+        if ( defined ( $git->{dir} ) and $git->{dir} ne "./" )
+        {
+            print "M U $checkout_path/$git->{dir}$git->{name}\n";
+        } else {
+            print "M U $checkout_path/$git->{name}\n";
+        }
+
+       # instruct client we're sending a file to put in this path
+       print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n";
+
+       print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
+
+        # this is an "entries" line
+        print "/$git->{name}/1.$git->{revision}///\n";
+        # permissions
+        print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
+
+        # transmit file
+        transmitfile($git->{filehash});
+    }
+
+    print "ok\n";
+
+    statecleanup();
+}
+
+# update \n
+#     Response expected: yes. Actually do a cvs update command. This uses any
+#     previous Argument, Directory, Entry, or Modified requests, if they have
+#     been sent. The last Directory sent specifies the working directory at the
+#     time of the operation. The -I option is not used--files which the client
+#     can decide whether to ignore are not mentioned and the client sends the
+#     Questionable request for others.
+sub req_update
+{
+    my ( $cmd, $data ) = @_;
+
+    $log->debug("req_update : " . ( defined($data) ? $data : "[NULL]" ));
+
+    argsplit("update");
+
+    #
+    # It may just be a client exploring the available heads/modules
+    # in that case, list them as top level directories and leave it
+    # at that. Eclipse uses this technique to offer you a list of
+    # projects (heads in this case) to checkout.
+    #
+    if ($state->{module} eq '') {
+        print "E cvs update: Updating .\n";
+       opendir HEADS, $state->{CVSROOT} . '/refs/heads';
+       while (my $head = readdir(HEADS)) {
+           if (-f $state->{CVSROOT} . '/refs/heads/' . $head) {
+               print "E cvs update: New directory `$head'\n";
+           }
+       }
+       closedir HEADS;
+       print "ok\n";
+       return 1;
+    }
+
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+
+    $updater->update();
+
+    argsfromdir($updater);
+
+    #$log->debug("update state : " . Dumper($state));
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        $log->debug("Processing file $filename");
+
+        # if we have a -C we should pretend we never saw modified stuff
+        if ( exists ( $state->{opt}{C} ) )
+        {
+            delete $state->{entries}{$filename}{modified_hash};
+            delete $state->{entries}{$filename}{modified_filename};
+            $state->{entries}{$filename}{unchanged} = 1;
+        }
+
+        my $meta;
+        if ( defined($state->{opt}{r}) and $state->{opt}{r} =~ /^1\.(\d+)/ )
+        {
+            $meta = $updater->getmeta($filename, $1);
+        } else {
+            $meta = $updater->getmeta($filename);
+        }
+
+        next unless ( $meta->{revision} );
+
+        my $oldmeta = $meta;
+
+        my $wrev = revparse($filename);
+
+        # If the working copy is an old revision, lets get that version too for comparison.
+        if ( defined($wrev) and $wrev != $meta->{revision} )
+        {
+            $oldmeta = $updater->getmeta($filename, $wrev);
+        }
+
+        #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
+
+        # Files are up to date if the working copy and repo copy have the same revision,
+        # and the working copy is unmodified _and_ the user hasn't specified -C
+        next if ( defined ( $wrev )
+                  and defined($meta->{revision})
+                  and $wrev == $meta->{revision}
+                  and $state->{entries}{$filename}{unchanged}
+                  and not exists ( $state->{opt}{C} ) );
+
+        # If the working copy and repo copy have the same revision,
+        # but the working copy is modified, tell the client it's modified
+        if ( defined ( $wrev )
+             and defined($meta->{revision})
+             and $wrev == $meta->{revision}
+             and not exists ( $state->{opt}{C} ) )
+        {
+            $log->info("Tell the client the file is modified");
+            print "MT text U\n";
+            print "MT fname $filename\n";
+            print "MT newline\n";
+            next;
+        }
+
+        if ( $meta->{filehash} eq "deleted" )
+        {
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+            $log->info("Removing '$filename' from working copy (no longer in the repo)");
+
+            print "E cvs update: `$filename' is no longer in the repository\n";
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} ) {
+               print "Removed $dirpart\n";
+               print "$filepart\n";
+           }
+        }
+        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
+               or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+        {
+            $log->info("Updating '$filename'");
+            # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
+            print "MT +updated\n";
+            print "MT text U\n";
+            print "MT fname $filename\n";
+            print "MT newline\n";
+            print "MT -updated\n";
+
+            my ( $filepart, $dirpart ) = filenamesplit($filename,1);
+
+           # Don't want to actually _DO_ the update if -n specified
+           unless ( $state->{globaloptions}{-n} )
+           {
+               if ( defined ( $wrev ) )
+               {
+                   # instruct client we're sending a file to put in this path as a replacement
+                   print "Update-existing $dirpart\n";
+                   $log->debug("Updating existing file 'Update-existing $dirpart'");
+               } else {
+                   # instruct client we're sending a file to put in this path as a new file
+                   print "Clear-static-directory $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+                   print "Clear-sticky $dirpart\n";
+                   print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
+
+                   $log->debug("Creating new file 'Created $dirpart'");
+                   print "Created $dirpart\n";
+               }
+               print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+
+               # this is an "entries" line
+               $log->debug("/$filepart/1.$meta->{revision}///");
+               print "/$filepart/1.$meta->{revision}///\n";
+
+               # permissions
+               $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+               print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+               # transmit file
+               transmitfile($meta->{filehash});
+           }
+        } else {
+            $log->info("Updating '$filename'");
+            my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
+
+            my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
+
+            chdir $dir;
+            my $file_local = $filepart . ".mine";
+            system("ln","-s",$state->{entries}{$filename}{modified_filename}, $file_local);
+            my $file_old = $filepart . "." . $oldmeta->{revision};
+            transmitfile($oldmeta->{filehash}, $file_old);
+            my $file_new = $filepart . "." . $meta->{revision};
+            transmitfile($meta->{filehash}, $file_new);
+
+            # we need to merge with the local changes ( M=successful merge, C=conflict merge )
+            $log->info("Merging $file_local, $file_old, $file_new");
+
+            $log->debug("Temporary directory for merge is $dir");
+
+            my $return = system("merge", $file_local, $file_old, $file_new);
+            $return >>= 8;
+
+            if ( $return == 0 )
+            {
+                $log->info("Merged successfully");
+                print "M M $filename\n";
+                $log->debug("Update-existing $dirpart");
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $dirpart\n";
+                    $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
+                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+                    $log->debug("/$filepart/1.$meta->{revision}///");
+                    print "/$filepart/1.$meta->{revision}///\n";
+                }
+            }
+            elsif ( $return == 1 )
+            {
+                $log->info("Merged with conflicts");
+                print "M C $filename\n";
+
+                # Don't want to actually _DO_ the update if -n specified
+                unless ( $state->{globaloptions}{-n} )
+                {
+                    print "Update-existing $dirpart\n";
+                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
+                    print "/$filepart/1.$meta->{revision}/+//\n";
+                }
+            }
+            else
+            {
+                $log->warn("Merge failed");
+                next;
+            }
+
+            # Don't want to actually _DO_ the update if -n specified
+            unless ( $state->{globaloptions}{-n} )
+            {
+                # permissions
+                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
+                print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";
+
+                # transmit file, format is single integer on a line by itself (file
+                # size) followed by the file contents
+                # TODO : we should copy files in blocks
+                my $data = `cat $file_local`;
+                $log->debug("File size : " . length($data));
+                print length($data) . "\n";
+                print $data;
+            }
+
+            chdir "/";
+        }
+
+    }
+
+    print "ok\n";
+}
+
+sub req_ci
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("ci");
+
+    #$log->debug("State : " . Dumper($state));
+
+    $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
+
+    if ( @ARGV && $ARGV[0] eq 'pserver')
+    {
+        print "error 1 pserver access cannot commit\n";
+        exit;
+    }
+
+    if ( -e $state->{CVSROOT} . "/index" )
+    {
+        $log->warn("file 'index' already exists in the git repository");
+        print "error 1 Index already exists in git repo\n";
+        exit;
+    }
+
+    my $lockfile = "$state->{CVSROOT}/refs/heads/$state->{module}.lock";
+    unless ( sysopen(LOCKFILE,$lockfile,O_EXCL|O_CREAT|O_WRONLY) )
+    {
+        $log->warn("lockfile '$lockfile' already exists, please try again");
+        print "error 1 Lock file '$lockfile' already exists, please try again\n";
+        exit;
+    }
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    my $tmpdir = tempdir ( DIR => $TEMP_DIR );
+    my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
+    $log->info("Lock successful, basing commit on '$tmpdir', index file is '$file_index'");
+
+    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    $ENV{GIT_INDEX_FILE} = $file_index;
+
+    chdir $tmpdir;
+
+    # populate the temporary index based
+    system("git-read-tree", $state->{module});
+    unless ($? == 0)
+    {
+       die "Error running git-read-tree $state->{module} $file_index $!";
+    }
+    $log->info("Created index '$file_index' with for head $state->{module} - exit status $?");
+
+
+    my @committedfiles = ();
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        my $committedfile = $filename;
+        $filename = filecleanup($filename);
+
+        next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
+
+        my $meta = $updater->getmeta($filename);
+
+        my $wrev = revparse($filename);
+
+        my ( $filepart, $dirpart ) = filenamesplit($filename);
+
+        # do a checkout of the file if it part of this tree
+        if ($wrev) {
+            system('git-checkout-index', '-f', '-u', $filename);
+            unless ($? == 0) {
+                die "Error running git-checkout-index -f -u $filename : $!";
+            }
+        }
+
+        my $addflag = 0;
+        my $rmflag = 0;
+        $rmflag = 1 if ( defined($wrev) and $wrev < 0 );
+        $addflag = 1 unless ( -e $filename );
+
+        # Do up to date checking
+        unless ( $addflag or $wrev == $meta->{revision} or ( $rmflag and -$wrev == $meta->{revision} ) )
+        {
+            # fail everything if an up to date check fails
+            print "error 1 Up to date check failed for $filename\n";
+            close LOCKFILE;
+            unlink($lockfile);
+            chdir "/";
+            exit;
+        }
+
+        push @committedfiles, $committedfile;
+        $log->info("Committing $filename");
+
+        system("mkdir","-p",$dirpart) unless ( -d $dirpart );
+
+        unless ( $rmflag )
+        {
+            $log->debug("rename $state->{entries}{$filename}{modified_filename} $filename");
+            rename $state->{entries}{$filename}{modified_filename},$filename;
+
+            # Calculate modes to remove
+            my $invmode = "";
+            foreach ( qw (r w x) ) { $invmode .= $_ unless ( $state->{entries}{$filename}{modified_mode} =~ /$_/ ); }
+
+            $log->debug("chmod u+" . $state->{entries}{$filename}{modified_mode} . "-" . $invmode . " $filename");
+            system("chmod","u+" .  $state->{entries}{$filename}{modified_mode} . "-" . $invmode, $filename);
+        }
+
+        if ( $rmflag )
+        {
+            $log->info("Removing file '$filename'");
+            unlink($filename);
+            system("git-update-index", "--remove", $filename);
+        }
+        elsif ( $addflag )
+        {
+            $log->info("Adding file '$filename'");
+            system("git-update-index", "--add", $filename);
+        } else {
+            $log->info("Updating file '$filename'");
+            system("git-update-index", $filename);
+        }
+    }
+
+    unless ( scalar(@committedfiles) > 0 )
+    {
+        print "E No files to commit\n";
+        print "ok\n";
+        close LOCKFILE;
+        unlink($lockfile);
+        chdir "/";
+        return;
+    }
+
+    my $treehash = `git-write-tree`;
+    my $parenthash = `cat $ENV{GIT_DIR}refs/heads/$state->{module}`;
+    chomp $treehash;
+    chomp $parenthash;
+
+    $log->debug("Treehash : $treehash, Parenthash : $parenthash");
+
+    # write our commit message out if we have one ...
+    my ( $msg_fh, $msg_filename ) = tempfile( DIR => $TEMP_DIR );
+    print $msg_fh $state->{opt}{m};# if ( exists ( $state->{opt}{m} ) );
+    print $msg_fh "\n\nvia git-CVS emulator\n";
+    close $msg_fh;
+
+    my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`;
+    $log->info("Commit hash : $commithash");
+
+    unless ( $commithash =~ /[a-zA-Z0-9]{40}/ )
+    {
+        $log->warn("Commit failed (Invalid commit hash)");
+        print "error 1 Commit failed (unknown reason)\n";
+        close LOCKFILE;
+        unlink($lockfile);
+        chdir "/";
+        exit;
+    }
+
+    open FILE, ">", "$ENV{GIT_DIR}refs/heads/$state->{module}";
+    print FILE $commithash;
+    close FILE;
+
+    $updater->update();
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @committedfiles )
+    {
+        $filename = filecleanup($filename);
+
+        my $meta = $updater->getmeta($filename);
+
+        my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
+
+        $log->debug("Checked-in $dirpart : $filename");
+
+        if ( $meta->{filehash} eq "deleted" )
+        {
+            print "Remove-entry $dirpart\n";
+            print "$filename\n";
+        } else {
+            print "Checked-in $dirpart\n";
+            print "$filename\n";
+            print "/$filepart/1.$meta->{revision}///\n";
+        }
+    }
+
+    close LOCKFILE;
+    unlink($lockfile);
+    chdir "/";
+
+    print "ok\n";
+}
+
+sub req_status
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("status");
+
+    $log->info("req_status : " . ( defined($data) ? $data : "[NULL]" ));
+    #$log->debug("status state : " . Dumper($state));
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    # if no files were specified, we need to work out what files we should be providing status on ...
+    argsfromdir($updater);
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        my $meta = $updater->getmeta($filename);
+        my $oldmeta = $meta;
+
+        my $wrev = revparse($filename);
+
+        # If the working copy is an old revision, lets get that version too for comparison.
+        if ( defined($wrev) and $wrev != $meta->{revision} )
+        {
+            $oldmeta = $updater->getmeta($filename, $wrev);
+        }
+
+        # TODO : All possible statuses aren't yet implemented
+        my $status;
+        # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified
+        $status = "Up-to-date" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision}
+                                    and
+                                    ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) )
+                                      or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta->{filehash} ) )
+                                   );
+
+        # Need checkout if the working copy has an older revision than the repo copy, and the working copy is unmodified
+        $status ||= "Needs Checkout" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev
+                                          and
+                                          ( $state->{entries}{$filename}{unchanged}
+                                            or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) )
+                                        );
+
+        # Need checkout if it exists in the repo but doesn't have a working copy
+        $status ||= "Needs Checkout" if ( not defined ( $wrev ) and defined ( $meta->{revision} ) );
+
+        # Locally modified if working copy and repo copy have the same revision but there are local changes
+        $status ||= "Locally Modified" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{modified_filename} );
+
+        # Needs Merge if working copy revision is less than repo copy and there are local changes
+        $status ||= "Needs Merge" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev and $state->{entries}{$filename}{modified_filename} );
+
+        $status ||= "Locally Added" if ( defined ( $state->{entries}{$filename}{revision} ) and not defined ( $meta->{revision} ) );
+        $status ||= "Locally Removed" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and -$wrev == $meta->{revision} );
+        $status ||= "Unresolved Conflict" if ( defined ( $state->{entries}{$filename}{conflict} ) and $state->{entries}{$filename}{conflict} =~ /^\+=/ );
+        $status ||= "File had conflicts on merge" if ( 0 );
+
+        $status ||= "Unknown";
+
+        print "M ===================================================================\n";
+        print "M File: $filename\tStatus: $status\n";
+        if ( defined($state->{entries}{$filename}{revision}) )
+        {
+            print "M Working revision:\t" . $state->{entries}{$filename}{revision} . "\n";
+        } else {
+            print "M Working revision:\tNo entry for $filename\n";
+        }
+        if ( defined($meta->{revision}) )
+        {
+            print "M Repository revision:\t1." . $meta->{revision} . "\t$state->{repository}/$filename,v\n";
+            print "M Sticky Tag:\t\t(none)\n";
+            print "M Sticky Date:\t\t(none)\n";
+            print "M Sticky Options:\t\t(none)\n";
+        } else {
+            print "M Repository revision:\tNo revision control file\n";
+        }
+        print "M\n";
+    }
+
+    print "ok\n";
+}
+
+sub req_diff
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("diff");
+
+    $log->debug("req_diff : " . ( defined($data) ? $data : "[NULL]" ));
+    #$log->debug("status state : " . Dumper($state));
+
+    my ($revision1, $revision2);
+    if ( defined ( $state->{opt}{r} ) and ref $state->{opt}{r} eq "ARRAY" )
+    {
+        $revision1 = $state->{opt}{r}[0];
+        $revision2 = $state->{opt}{r}[1];
+    } else {
+        $revision1 = $state->{opt}{r};
+    }
+
+    $revision1 =~ s/^1\.// if ( defined ( $revision1 ) );
+    $revision2 =~ s/^1\.// if ( defined ( $revision2 ) );
+
+    $log->debug("Diffing revisions " . ( defined($revision1) ? $revision1 : "[NULL]" ) . " and " . ( defined($revision2) ? $revision2 : "[NULL]" ) );
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    # if no files were specified, we need to work out what files we should be providing status on ...
+    argsfromdir($updater);
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        my ( $fh, $file1, $file2, $meta1, $meta2, $filediff );
+
+        my $wrev = revparse($filename);
+
+        # We need _something_ to diff against
+        next unless ( defined ( $wrev ) );
+
+        # if we have a -r switch, use it
+        if ( defined ( $revision1 ) )
+        {
+            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            $meta1 = $updater->getmeta($filename, $revision1);
+            unless ( defined ( $meta1 ) and $meta1->{filehash} ne "deleted" )
+            {
+                print "E File $filename at revision 1.$revision1 doesn't exist\n";
+                next;
+            }
+            transmitfile($meta1->{filehash}, $file1);
+        }
+        # otherwise we just use the working copy revision
+        else
+        {
+            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            $meta1 = $updater->getmeta($filename, $wrev);
+            transmitfile($meta1->{filehash}, $file1);
+        }
+
+        # if we have a second -r switch, use it too
+        if ( defined ( $revision2 ) )
+        {
+            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            $meta2 = $updater->getmeta($filename, $revision2);
+
+            unless ( defined ( $meta2 ) and $meta2->{filehash} ne "deleted" )
+            {
+                print "E File $filename at revision 1.$revision2 doesn't exist\n";
+                next;
+            }
+
+            transmitfile($meta2->{filehash}, $file2);
+        }
+        # otherwise we just use the working copy
+        else
+        {
+            $file2 = $state->{entries}{$filename}{modified_filename};
+        }
+
+        # if we have been given -r, and we don't have a $file2 yet, lets get one
+        if ( defined ( $revision1 ) and not defined ( $file2 ) )
+        {
+            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            $meta2 = $updater->getmeta($filename, $wrev);
+            transmitfile($meta2->{filehash}, $file2);
+        }
+
+        # We need to have retrieved something useful
+        next unless ( defined ( $meta1 ) );
+
+        # Files to date if the working copy and repo copy have the same revision, and the working copy is unmodified
+        next if ( not defined ( $meta2 ) and $wrev == $meta1->{revision}
+                  and
+                   ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) )
+                     or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta1->{filehash} ) )
+                  );
+
+        # Apparently we only show diffs for locally modified files
+        next unless ( defined($meta2) or defined ( $state->{entries}{$filename}{modified_filename} ) );
+
+        print "M Index: $filename\n";
+        print "M ===================================================================\n";
+        print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n";
+        print "M retrieving revision 1.$meta1->{revision}\n" if ( defined ( $meta1 ) );
+        print "M retrieving revision 1.$meta2->{revision}\n" if ( defined ( $meta2 ) );
+        print "M diff ";
+        foreach my $opt ( keys %{$state->{opt}} )
+        {
+            if ( ref $state->{opt}{$opt} eq "ARRAY" )
+            {
+                foreach my $value ( @{$state->{opt}{$opt}} )
+                {
+                    print "-$opt $value ";
+                }
+            } else {
+                print "-$opt ";
+                print "$state->{opt}{$opt} " if ( defined ( $state->{opt}{$opt} ) );
+            }
+        }
+        print "$filename\n";
+
+        $log->info("Diffing $filename -r $meta1->{revision} -r " . ( $meta2->{revision} or "workingcopy" ));
+
+        ( $fh, $filediff ) = tempfile ( DIR => $TEMP_DIR );
+
+        if ( exists $state->{opt}{u} )
+        {
+            system("diff -u -L '$filename revision 1.$meta1->{revision}' -L '$filename " . ( defined($meta2->{revision}) ? "revision 1.$meta2->{revision}" : "working copy" ) . "' $file1 $file2 > $filediff");
+        } else {
+            system("diff $file1 $file2 > $filediff");
+        }
+
+        while ( <$fh> )
+        {
+            print "M $_";
+        }
+        close $fh;
+    }
+
+    print "ok\n";
+}
+
+sub req_log
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("log");
+
+    $log->debug("req_log : " . ( defined($data) ? $data : "[NULL]" ));
+    #$log->debug("log state : " . Dumper($state));
+
+    my ( $minrev, $maxrev );
+    if ( defined ( $state->{opt}{r} ) and $state->{opt}{r} =~ /([\d.]+)?(::?)([\d.]+)?/ )
+    {
+        my $control = $2;
+        $minrev = $1;
+        $maxrev = $3;
+        $minrev =~ s/^1\.// if ( defined ( $minrev ) );
+        $maxrev =~ s/^1\.// if ( defined ( $maxrev ) );
+        $minrev++ if ( defined($minrev) and $control eq "::" );
+    }
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    # if no files were specified, we need to work out what files we should be providing status on ...
+    argsfromdir($updater);
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        my $headmeta = $updater->getmeta($filename);
+
+        my $revisions = $updater->getlog($filename);
+        my $totalrevisions = scalar(@$revisions);
+
+        if ( defined ( $minrev ) )
+        {
+            $log->debug("Removing revisions less than $minrev");
+            while ( scalar(@$revisions) > 0 and $revisions->[-1]{revision} < $minrev )
+            {
+                pop @$revisions;
+            }
+        }
+        if ( defined ( $maxrev ) )
+        {
+            $log->debug("Removing revisions greater than $maxrev");
+            while ( scalar(@$revisions) > 0 and $revisions->[0]{revision} > $maxrev )
+            {
+                shift @$revisions;
+            }
+        }
+
+        next unless ( scalar(@$revisions) );
+
+        print "M \n";
+        print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n";
+        print "M Working file: $filename\n";
+        print "M head: 1.$headmeta->{revision}\n";
+        print "M branch:\n";
+        print "M locks: strict\n";
+        print "M access list:\n";
+        print "M symbolic names:\n";
+        print "M keyword substitution: kv\n";
+        print "M total revisions: $totalrevisions;\tselected revisions: " . scalar(@$revisions) . "\n";
+        print "M description:\n";
+
+        foreach my $revision ( @$revisions )
+        {
+            print "M ----------------------------\n";
+            print "M revision 1.$revision->{revision}\n";
+            # reformat the date for log output
+            $revision->{modified} = sprintf('%04d/%02d/%02d %s', $3, $DATE_LIST->{$2}, $1, $4 ) if ( $revision->{modified} =~ /(\d+)\s+(\w+)\s+(\d+)\s+(\S+)/ and defined($DATE_LIST->{$2}) );
+            $revision->{author} =~ s/\s+.*//;
+            $revision->{author} =~ s/^(.{8}).*/$1/;
+            print "M date: $revision->{modified};  author: $revision->{author};  state: " . ( $revision->{filehash} eq "deleted" ? "dead" : "Exp" ) . ";  lines: +2 -3\n";
+            my $commitmessage = $updater->commitmessage($revision->{commithash});
+            $commitmessage =~ s/^/M /mg;
+            print $commitmessage . "\n";
+        }
+        print "M =============================================================================\n";
+    }
+
+    print "ok\n";
+}
+
+sub req_annotate
+{
+    my ( $cmd, $data ) = @_;
+
+    argsplit("annotate");
+
+    $log->info("req_annotate : " . ( defined($data) ? $data : "[NULL]" ));
+    #$log->debug("status state : " . Dumper($state));
+
+    # Grab a handle to the SQLite db and do any necessary updates
+    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
+    $updater->update();
+
+    # if no files were specified, we need to work out what files we should be providing annotate on ...
+    argsfromdir($updater);
+
+    # we'll need a temporary checkout dir
+    my $tmpdir = tempdir ( DIR => $TEMP_DIR );
+    my ( undef, $file_index ) = tempfile ( DIR => $TEMP_DIR, OPEN => 0 );
+    $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'");
+
+    $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    $ENV{GIT_INDEX_FILE} = $file_index;
+
+    chdir $tmpdir;
+
+    # foreach file specified on the commandline ...
+    foreach my $filename ( @{$state->{args}} )
+    {
+        $filename = filecleanup($filename);
+
+        my $meta = $updater->getmeta($filename);
+
+        next unless ( $meta->{revision} );
+
+       # get all the commits that this file was in
+       # in dense format -- aka skip dead revisions
+        my $revisions   = $updater->gethistorydense($filename);
+       my $lastseenin  = $revisions->[0][2];
+
+       # populate the temporary index based on the latest commit were we saw
+       # the file -- but do it cheaply without checking out any files
+       # TODO: if we got a revision from the client, use that instead
+       # to look up the commithash in sqlite (still good to default to
+       # the current head as we do now)
+       system("git-read-tree", $lastseenin);
+       unless ($? == 0)
+       {
+           die "Error running git-read-tree $lastseenin $file_index $!";
+       }
+       $log->info("Created index '$file_index' with commit $lastseenin - exit status $?");
+
+        # do a checkout of the file
+        system('git-checkout-index', '-f', '-u', $filename);
+        unless ($? == 0) {
+            die "Error running git-checkout-index -f -u $filename : $!";
+        }
+
+        $log->info("Annotate $filename");
+
+        # Prepare a file with the commits from the linearized
+        # history that annotate should know about. This prevents
+        # git-jsannotate telling us about commits we are hiding
+        # from the client.
+
+        open(ANNOTATEHINTS, ">$tmpdir/.annotate_hints") or die "Error opening > $tmpdir/.annotate_hints $!";
+        for (my $i=0; $i < @$revisions; $i++)
+        {
+            print ANNOTATEHINTS $revisions->[$i][2];
+            if ($i+1 < @$revisions) { # have we got a parent?
+                print ANNOTATEHINTS ' ' . $revisions->[$i+1][2];
+            }
+            print ANNOTATEHINTS "\n";
+        }
+
+        print ANNOTATEHINTS "\n";
+        close ANNOTATEHINTS;
+
+        my $annotatecmd = 'git-annotate';
+        open(ANNOTATE, "-|", $annotatecmd, '-l', '-S', "$tmpdir/.annotate_hints", $filename)
+           or die "Error invoking $annotatecmd -l -S $tmpdir/.annotate_hints $filename : $!";
+        my $metadata = {};
+        print "E Annotations for $filename\n";
+        print "E ***************\n";
+        while ( <ANNOTATE> )
+        {
+            if (m/^([a-zA-Z0-9]{40})\t\([^\)]*\)(.*)$/i)
+            {
+                my $commithash = $1;
+                my $data = $2;
+                unless ( defined ( $metadata->{$commithash} ) )
+                {
+                    $metadata->{$commithash} = $updater->getmeta($filename, $commithash);
+                    $metadata->{$commithash}{author} =~ s/\s+.*//;
+                    $metadata->{$commithash}{author} =~ s/^(.{8}).*/$1/;
+                    $metadata->{$commithash}{modified} = sprintf("%02d-%s-%02d", $1, $2, $3) if ( $metadata->{$commithash}{modified} =~ /^(\d+)\s(\w+)\s\d\d(\d\d)/ );
+                }
+                printf("M 1.%-5d      (%-8s %10s): %s\n",
+                    $metadata->{$commithash}{revision},
+                    $metadata->{$commithash}{author},
+                    $metadata->{$commithash}{modified},
+                    $data
+                );
+            } else {
+                $log->warn("Error in annotate output! LINE: $_");
+                print "E Annotate error \n";
+                next;
+            }
+        }
+        close ANNOTATE;
+    }
+
+    # done; get out of the tempdir
+    chdir "/";
+
+    print "ok\n";
+
+}
+
+# This method takes the state->{arguments} array and produces two new arrays.
+# The first is $state->{args} which is everything before the '--' argument, and
+# the second is $state->{files} which is everything after it.
+sub argsplit
+{
+    return unless( defined($state->{arguments}) and ref $state->{arguments} eq "ARRAY" );
+
+    my $type = shift;
+
+    $state->{args} = [];
+    $state->{files} = [];
+    $state->{opt} = {};
+
+    if ( defined($type) )
+    {
+        my $opt = {};
+        $opt = { A => 0, N => 0, P => 0, R => 0, c => 0, f => 0, l => 0, n => 0, p => 0, s => 0, r => 1, D => 1, d => 1, k => 1, j => 1, } if ( $type eq "co" );
+        $opt = { v => 0, l => 0, R => 0 } if ( $type eq "status" );
+        $opt = { A => 0, P => 0, C => 0, d => 0, f => 0, l => 0, R => 0, p => 0, k => 1, r => 1, D => 1, j => 1, I => 1, W => 1 } if ( $type eq "update" );
+        $opt = { l => 0, R => 0, k => 1, D => 1, D => 1, r => 2 } if ( $type eq "diff" );
+        $opt = { c => 0, R => 0, l => 0, f => 0, F => 1, m => 1, r => 1 } if ( $type eq "ci" );
+        $opt = { k => 1, m => 1 } if ( $type eq "add" );
+        $opt = { f => 0, l => 0, R => 0 } if ( $type eq "remove" );
+        $opt = { l => 0, b => 0, h => 0, R => 0, t => 0, N => 0, S => 0, r => 1, d => 1, s => 1, w => 1 } if ( $type eq "log" );
+
+
+        while ( scalar ( @{$state->{arguments}} ) > 0 )
+        {
+            my $arg = shift @{$state->{arguments}};
+
+            next if ( $arg eq "--" );
+            next unless ( $arg =~ /\S/ );
+
+            # if the argument looks like a switch
+            if ( $arg =~ /^-(\w)(.*)/ )
+            {
+                # if it's a switch that takes an argument
+                if ( $opt->{$1} )
+                {
+                    # If this switch has already been provided
+                    if ( $opt->{$1} > 1 and exists ( $state->{opt}{$1} ) )
+                    {
+                        $state->{opt}{$1} = [ $state->{opt}{$1} ];
+                        if ( length($2) > 0 )
+                        {
+                            push @{$state->{opt}{$1}},$2;
+                        } else {
+                            push @{$state->{opt}{$1}}, shift @{$state->{arguments}};
+                        }
+                    } else {
+                        # if there's extra data in the arg, use that as the argument for the switch
+                        if ( length($2) > 0 )
+                        {
+                            $state->{opt}{$1} = $2;
+                        } else {
+                            $state->{opt}{$1} = shift @{$state->{arguments}};
+                        }
+                    }
+                } else {
+                    $state->{opt}{$1} = undef;
+                }
+            }
+            else
+            {
+                push @{$state->{args}}, $arg;
+            }
+        }
+    }
+    else
+    {
+        my $mode = 0;
+
+        foreach my $value ( @{$state->{arguments}} )
+        {
+            if ( $value eq "--" )
+            {
+                $mode++;
+                next;
+            }
+            push @{$state->{args}}, $value if ( $mode == 0 );
+            push @{$state->{files}}, $value if ( $mode == 1 );
+        }
+    }
+}
+
+# This method uses $state->{directory} to populate $state->{args} with a list of filenames
+sub argsfromdir
+{
+    my $updater = shift;
+
+    $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
+
+    return if ( scalar ( @{$state->{args}} ) > 1 );
+
+    if ( scalar(@{$state->{args}}) == 1 )
+    {
+        my $arg = $state->{args}[0];
+        $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
+
+        $log->info("Only one arg specified, checking for directory expansion on '$arg'");
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg  );
+            push @{$state->{args}}, $file->{name};
+        }
+
+        shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
+    } else {
+        $log->info("Only one arg specified, populating file list automatically");
+
+        $state->{args} = [];
+
+        foreach my $file ( @{$updater->gethead} )
+        {
+            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
+            next unless ( $file->{name} =~ s/^$state->{prependdir}// );
+            push @{$state->{args}}, $file->{name};
+        }
+    }
+}
+
+# This method cleans up the $state variable after a command that uses arguments has run
+sub statecleanup
+{
+    $state->{files} = [];
+    $state->{args} = [];
+    $state->{arguments} = [];
+    $state->{entries} = {};
+}
+
+sub revparse
+{
+    my $filename = shift;
+
+    return undef unless ( defined ( $state->{entries}{$filename}{revision} ) );
+
+    return $1 if ( $state->{entries}{$filename}{revision} =~ /^1\.(\d+)/ );
+    return -$1 if ( $state->{entries}{$filename}{revision} =~ /^-1\.(\d+)/ );
+
+    return undef;
+}
+
+# This method takes a file hash and does a CVS "file transfer" which transmits the
+# size of the file, and then the file contents.
+# If a second argument $targetfile is given, the file is instead written out to
+# a file by the name of $targetfile
+sub transmitfile
+{
+    my $filehash = shift;
+    my $targetfile = shift;
+
+    if ( defined ( $filehash ) and $filehash eq "deleted" )
+    {
+        $log->warn("filehash is 'deleted'");
+        return;
+    }
+
+    die "Need filehash" unless ( defined ( $filehash ) and $filehash =~ /^[a-zA-Z0-9]{40}$/ );
+
+    my $type = `git-cat-file -t $filehash`;
+    chomp $type;
+
+    die ( "Invalid type '$type' (expected 'blob')" ) unless ( defined ( $type ) and $type eq "blob" );
+
+    my $size = `git-cat-file -s $filehash`;
+    chomp $size;
+
+    $log->debug("transmitfile($filehash) size=$size, type=$type");
+
+    if ( open my $fh, '-|', "git-cat-file", "blob", $filehash )
+    {
+        if ( defined ( $targetfile ) )
+        {
+            open NEWFILE, ">", $targetfile or die("Couldn't open '$targetfile' for writing : $!");
+            print NEWFILE $_ while ( <$fh> );
+            close NEWFILE;
+        } else {
+            print "$size\n";
+            print while ( <$fh> );
+        }
+        close $fh or die ("Couldn't close filehandle for transmitfile()");
+    } else {
+        die("Couldn't execute git-cat-file");
+    }
+}
+
+# This method takes a file name, and returns ( $dirpart, $filepart ) which
+# refers to the directory portion and the file portion of the filename
+# respectively
+sub filenamesplit
+{
+    my $filename = shift;
+    my $fixforlocaldir = shift;
+
+    my ( $filepart, $dirpart ) = ( $filename, "." );
+    ( $filepart, $dirpart ) = ( $2, $1 ) if ( $filename =~ /(.*)\/(.*)/ );
+    $dirpart .= "/";
+
+    if ( $fixforlocaldir )
+    {
+        $dirpart =~ s/^$state->{prependdir}//;
+    }
+
+    return ( $filepart, $dirpart );
+}
+
+sub filecleanup
+{
+    my $filename = shift;
+
+    return undef unless(defined($filename));
+    if ( $filename =~ /^\// )
+    {
+        print "E absolute filenames '$filename' not supported by server\n";
+        return undef;
+    }
+
+    $filename =~ s/^\.\///g;
+    $filename = $state->{prependdir} . $filename;
+    return $filename;
+}
+
+package GITCVS::log;
+
+####
+#### Copyright The Open University UK - 2006.
+####
+#### Authors: Martyn Smith    <martyn@catalyst.net.nz>
+####          Martin Langhoff <martin@catalyst.net.nz>
+####
+####
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+GITCVS::log
+
+=head1 DESCRIPTION
+
+This module provides very crude logging with a similar interface to
+Log::Log4perl
+
+=head1 METHODS
+
+=cut
+
+=head2 new
+
+Creates a new log object, optionally you can specify a filename here to
+indicate the file to log to. If no log file is specified, you can specify one
+later with method setfile, or indicate you no longer want logging with method
+nofile.
+
+Until one of these methods is called, all log calls will buffer messages ready
+to write out.
+
+=cut
+sub new
+{
+    my $class = shift;
+    my $filename = shift;
+
+    my $self = {};
+
+    bless $self, $class;
+
+    if ( defined ( $filename ) )
+    {
+        open $self->{fh}, ">>", $filename or die("Couldn't open '$filename' for writing : $!");
+    }
+
+    return $self;
+}
+
+=head2 setfile
+
+This methods takes a filename, and attempts to open that file as the log file.
+If successful, all buffered data is written out to the file, and any further
+logging is written directly to the file.
+
+=cut
+sub setfile
+{
+    my $self = shift;
+    my $filename = shift;
+
+    if ( defined ( $filename ) )
+    {
+        open $self->{fh}, ">>", $filename or die("Couldn't open '$filename' for writing : $!");
+    }
+
+    return unless ( defined ( $self->{buffer} ) and ref $self->{buffer} eq "ARRAY" );
+
+    while ( my $line = shift @{$self->{buffer}} )
+    {
+        print {$self->{fh}} $line;
+    }
+}
+
+=head2 nofile
+
+This method indicates no logging is going to be used. It flushes any entries in
+the internal buffer, and sets a flag to ensure no further data is put there.
+
+=cut
+sub nofile
+{
+    my $self = shift;
+
+    $self->{nolog} = 1;
+
+    return unless ( defined ( $self->{buffer} ) and ref $self->{buffer} eq "ARRAY" );
+
+    $self->{buffer} = [];
+}
+
+=head2 _logopen
+
+Internal method. Returns true if the log file is open, false otherwise.
+
+=cut
+sub _logopen
+{
+    my $self = shift;
+
+    return 1 if ( defined ( $self->{fh} ) and ref $self->{fh} eq "GLOB" );
+    return 0;
+}
+
+=head2 debug info warn fatal
+
+These four methods are wrappers to _log. They provide the actual interface for
+logging data.
+
+=cut
+sub debug { my $self = shift; $self->_log("debug", @_); }
+sub info  { my $self = shift; $self->_log("info" , @_); }
+sub warn  { my $self = shift; $self->_log("warn" , @_); }
+sub fatal { my $self = shift; $self->_log("fatal", @_); }
+
+=head2 _log
+
+This is an internal method called by the logging functions. It generates a
+timestamp and pushes the logged line either to file, or internal buffer.
+
+=cut
+sub _log
+{
+    my $self = shift;
+    my $level = shift;
+
+    return if ( $self->{nolog} );
+
+    my @time = localtime;
+    my $timestring = sprintf("%4d-%02d-%02d %02d:%02d:%02d : %-5s",
+        $time[5] + 1900,
+        $time[4] + 1,
+        $time[3],
+        $time[2],
+        $time[1],
+        $time[0],
+        uc $level,
+    );
+
+    if ( $self->_logopen )
+    {
+        print {$self->{fh}} $timestring . " - " . join(" ",@_) . "\n";
+    } else {
+        push @{$self->{buffer}}, $timestring . " - " . join(" ",@_) . "\n";
+    }
+}
+
+=head2 DESTROY
+
+This method simply closes the file handle if one is open
+
+=cut
+sub DESTROY
+{
+    my $self = shift;
+
+    if ( $self->_logopen )
+    {
+        close $self->{fh};
+    }
+}
+
+package GITCVS::updater;
+
+####
+#### Copyright The Open University UK - 2006.
+####
+#### Authors: Martyn Smith    <martyn@catalyst.net.nz>
+####          Martin Langhoff <martin@catalyst.net.nz>
+####
+####
+
+use strict;
+use warnings;
+use DBI;
+
+=head1 METHODS
+
+=cut
+
+=head2 new
+
+=cut
+sub new
+{
+    my $class = shift;
+    my $config = shift;
+    my $module = shift;
+    my $log = shift;
+
+    die "Need to specify a git repository" unless ( defined($config) and -d $config );
+    die "Need to specify a module" unless ( defined($module) );
+
+    $class = ref($class) || $class;
+
+    my $self = {};
+
+    bless $self, $class;
+
+    $self->{dbdir} = $config . "/";
+    die "Database dir '$self->{dbdir}' isn't a directory" unless ( defined($self->{dbdir}) and -d $self->{dbdir} );
+
+    $self->{module} = $module;
+    $self->{file} = $self->{dbdir} . "/gitcvs.$module.sqlite";
+
+    $self->{git_path} = $config . "/";
+
+    $self->{log} = $log;
+
+    die "Git repo '$self->{git_path}' doesn't exist" unless ( -d $self->{git_path} );
+
+    $self->{dbh} = DBI->connect("dbi:SQLite:dbname=" . $self->{file},"","");
+
+    $self->{tables} = {};
+    foreach my $table ( $self->{dbh}->tables )
+    {
+        $table =~ s/^"//;
+        $table =~ s/"$//;
+        $self->{tables}{$table} = 1;
+    }
+
+    # Construct the revision table if required
+    unless ( $self->{tables}{revision} )
+    {
+        $self->{dbh}->do("
+            CREATE TABLE revision (
+                name       TEXT NOT NULL,
+                revision   INTEGER NOT NULL,
+                filehash   TEXT NOT NULL,
+                commithash TEXT NOT NULL,
+                author     TEXT NOT NULL,
+                modified   TEXT NOT NULL,
+                mode       TEXT NOT NULL
+            )
+        ");
+    }
+
+    # Construct the revision table if required
+    unless ( $self->{tables}{head} )
+    {
+        $self->{dbh}->do("
+            CREATE TABLE head (
+                name       TEXT NOT NULL,
+                revision   INTEGER NOT NULL,
+                filehash   TEXT NOT NULL,
+                commithash TEXT NOT NULL,
+                author     TEXT NOT NULL,
+                modified   TEXT NOT NULL,
+                mode       TEXT NOT NULL
+            )
+        ");
+    }
+
+    # Construct the properties table if required
+    unless ( $self->{tables}{properties} )
+    {
+        $self->{dbh}->do("
+            CREATE TABLE properties (
+                key        TEXT NOT NULL PRIMARY KEY,
+                value      TEXT
+            )
+        ");
+    }
+
+    # Construct the commitmsgs table if required
+    unless ( $self->{tables}{commitmsgs} )
+    {
+        $self->{dbh}->do("
+            CREATE TABLE commitmsgs (
+                key        TEXT NOT NULL PRIMARY KEY,
+                value      TEXT
+            )
+        ");
+    }
+
+    return $self;
+}
+
+=head2 update
+
+=cut
+sub update
+{
+    my $self = shift;
+
+    # first lets get the commit list
+    $ENV{GIT_DIR} = $self->{git_path};
+
+    # prepare database queries
+    my $db_insert_rev = $self->{dbh}->prepare_cached("INSERT INTO revision (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+    my $db_insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO commitmsgs (key, value) VALUES (?,?)",{},1);
+    my $db_delete_head = $self->{dbh}->prepare_cached("DELETE FROM head",{},1);
+    my $db_insert_head = $self->{dbh}->prepare_cached("INSERT INTO head (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+
+    my $commitinfo = `git-cat-file commit $self->{module} 2>&1`;
+    unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
+    {
+        die("Invalid module '$self->{module}'");
+    }
+
+
+    my $git_log;
+    my $lastcommit = $self->_get_prop("last_commit");
+
+    # Start exclusive lock here...
+    $self->{dbh}->begin_work() or die "Cannot lock database for BEGIN";
+
+    # TODO: log processing is memory bound
+    # if we can parse into a 2nd file that is in reverse order
+    # we can probably do something really efficient
+    my @git_log_params = ('--pretty', '--parents', '--topo-order');
+
+    if (defined $lastcommit) {
+        push @git_log_params, "$lastcommit..$self->{module}";
+    } else {
+        push @git_log_params, $self->{module};
+    }
+    # git-rev-list is the backend / plumbing version of git-log
+    open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
+
+    my @commits;
+
+    my %commit = ();
+
+    while ( <GITLOG> )
+    {
+        chomp;
+        if (m/^commit\s+(.*)$/) {
+            # on ^commit lines put the just seen commit in the stack
+            # and prime things for the next one
+            if (keys %commit) {
+                my %copy = %commit;
+                unshift @commits, \%copy;
+                %commit = ();
+            }
+            my @parents = split(m/\s+/, $1);
+            $commit{hash} = shift @parents;
+            $commit{parents} = \@parents;
+        } elsif (m/^(\w+?):\s+(.*)$/ && !exists($commit{message})) {
+            # on rfc822-like lines seen before we see any message,
+            # lowercase the entry and put it in the hash as key-value
+            $commit{lc($1)} = $2;
+        } else {
+            # message lines - skip initial empty line
+            # and trim whitespace
+            if (!exists($commit{message}) && m/^\s*$/) {
+                # define it to mark the end of headers
+                $commit{message} = '';
+                next;
+            }
+            s/^\s+//; s/\s+$//; # trim ws
+            $commit{message} .= $_ . "\n";
+        }
+    }
+    close GITLOG;
+
+    unshift @commits, \%commit if ( keys %commit );
+
+    # Now all the commits are in the @commits bucket
+    # ordered by time DESC. for each commit that needs processing,
+    # determine whether it's following the last head we've seen or if
+    # it's on its own branch, grab a file list, and add whatever's changed
+    # NOTE: $lastcommit refers to the last commit from previous run
+    #       $lastpicked is the last commit we picked in this run
+    my $lastpicked;
+    my $head = {};
+    if (defined $lastcommit) {
+        $lastpicked = $lastcommit;
+    }
+
+    my $committotal = scalar(@commits);
+    my $commitcount = 0;
+
+    # Load the head table into $head (for cached lookups during the update process)
+    foreach my $file ( @{$self->gethead()} )
+    {
+        $head->{$file->{name}} = $file;
+    }
+
+    foreach my $commit ( @commits )
+    {
+        $self->{log}->debug("GITCVS::updater - Processing commit $commit->{hash} (" . (++$commitcount) . " of $committotal)");
+        if (defined $lastpicked)
+        {
+            if (!in_array($lastpicked, @{$commit->{parents}}))
+            {
+                # skip, we'll see this delta
+                # as part of a merge later
+                # warn "skipping off-track  $commit->{hash}\n";
+                next;
+            } elsif (@{$commit->{parents}} > 1) {
+                # it is a merge commit, for each parent that is
+                # not $lastpicked, see if we can get a log
+                # from the merge-base to that parent to put it
+                # in the message as a merge summary.
+                my @parents = @{$commit->{parents}};
+                foreach my $parent (@parents) {
+                    # git-merge-base can potentially (but rarely) throw
+                    # several candidate merge bases. let's assume
+                    # that the first one is the best one.
+                    if ($parent eq $lastpicked) {
+                        next;
+                    }
+                    open my $p, 'git-merge-base '. $lastpicked . ' '
+                    . $parent . '|';
+                    my @output = (<$p>);
+                    close $p;
+                    my $base = join('', @output);
+                    chomp $base;
+                    if ($base) {
+                        my @merged;
+                        # print "want to log between  $base $parent \n";
+                        open(GITLOG, '-|', 'git-log', "$base..$parent")
+                        or die "Cannot call git-log: $!";
+                        my $mergedhash;
+                        while (<GITLOG>) {
+                            chomp;
+                            if (!defined $mergedhash) {
+                                if (m/^commit\s+(.+)$/) {
+                                    $mergedhash = $1;
+                                } else {
+                                    next;
+                                }
+                            } else {
+                                # grab the first line that looks non-rfc822
+                                # aka has content after leading space
+                                if (m/^\s+(\S.*)$/) {
+                                    my $title = $1;
+                                    $title = substr($title,0,100); # truncate
+                                    unshift @merged, "$mergedhash $title";
+                                    undef $mergedhash;
+                                }
+                            }
+                        }
+                        close GITLOG;
+                        if (@merged) {
+                            $commit->{mergemsg} = $commit->{message};
+                            $commit->{mergemsg} .= "\nSummary of merged commits:\n\n";
+                            foreach my $summary (@merged) {
+                                $commit->{mergemsg} .= "\t$summary\n";
+                            }
+                            $commit->{mergemsg} .= "\n\n";
+                            # print "Message for $commit->{hash} \n$commit->{mergemsg}";
+                        }
+                    }
+                }
+            }
+        }
+
+        # convert the date to CVS-happy format
+        $commit->{date} = "$2 $1 $4 $3 $5" if ( $commit->{date} =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\d+)\s+([+-]\d+)$/ );
+
+        if ( defined ( $lastpicked ) )
+        {
+            my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!");
+            while ( <FILELIST> )
+            {
+                unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)\s+(.*)$/o )
+                {
+                    die("Couldn't process git-diff-tree line : $_");
+                }
+
+                # $log->debug("File mode=$1, hash=$2, change=$3, name=$4");
+
+                my $git_perms = "";
+                $git_perms .= "r" if ( $1 & 4 );
+                $git_perms .= "w" if ( $1 & 2 );
+                $git_perms .= "x" if ( $1 & 1 );
+                $git_perms = "rw" if ( $git_perms eq "" );
+
+                if ( $3 eq "D" )
+                {
+                    #$log->debug("DELETE   $4");
+                    $head->{$4} = {
+                        name => $4,
+                        revision => $head->{$4}{revision} + 1,
+                        filehash => "deleted",
+                        commithash => $commit->{hash},
+                        modified => $commit->{date},
+                        author => $commit->{author},
+                        mode => $git_perms,
+                    };
+                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                }
+                elsif ( $3 eq "M" )
+                {
+                    #$log->debug("MODIFIED $4");
+                    $head->{$4} = {
+                        name => $4,
+                        revision => $head->{$4}{revision} + 1,
+                        filehash => $2,
+                        commithash => $commit->{hash},
+                        modified => $commit->{date},
+                        author => $commit->{author},
+                        mode => $git_perms,
+                    };
+                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                }
+                elsif ( $3 eq "A" )
+                {
+                    #$log->debug("ADDED    $4");
+                    $head->{$4} = {
+                        name => $4,
+                        revision => 1,
+                        filehash => $2,
+                        commithash => $commit->{hash},
+                        modified => $commit->{date},
+                        author => $commit->{author},
+                        mode => $git_perms,
+                    };
+                    $db_insert_rev->execute($4, $head->{$4}{revision}, $2, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                }
+                else
+                {
+                    $log->warn("UNKNOWN FILE CHANGE mode=$1, hash=$2, change=$3, name=$4");
+                    die;
+                }
+            }
+            close FILELIST;
+        } else {
+            # this is used to detect files removed from the repo
+            my $seen_files = {};
+
+            my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!");
+            while ( <FILELIST> )
+            {
+                unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\s+(.*)$/o )
+                {
+                    die("Couldn't process git-ls-tree line : $_");
+                }
+
+                my ( $git_perms, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
+
+                $seen_files->{$git_filename} = 1;
+
+                my ( $oldhash, $oldrevision, $oldmode ) = (
+                    $head->{$git_filename}{filehash},
+                    $head->{$git_filename}{revision},
+                    $head->{$git_filename}{mode}
+                );
+
+                if ( $git_perms =~ /^\d\d\d(\d)\d\d/o )
+                {
+                    $git_perms = "";
+                    $git_perms .= "r" if ( $1 & 4 );
+                    $git_perms .= "w" if ( $1 & 2 );
+                    $git_perms .= "x" if ( $1 & 1 );
+                } else {
+                    $git_perms = "rw";
+                }
+
+                # unless the file exists with the same hash, we need to update it ...
+                unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $git_perms )
+                {
+                    my $newrevision = ( $oldrevision or 0 ) + 1;
+
+                    $head->{$git_filename} = {
+                        name => $git_filename,
+                        revision => $newrevision,
+                        filehash => $git_hash,
+                        commithash => $commit->{hash},
+                        modified => $commit->{date},
+                        author => $commit->{author},
+                        mode => $git_perms,
+                    };
+
+
+                    $db_insert_rev->execute($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                }
+            }
+            close FILELIST;
+
+            # Detect deleted files
+            foreach my $file ( keys %$head )
+            {
+                unless ( exists $seen_files->{$file} or $head->{$file}{filehash} eq "deleted" )
+                {
+                    $head->{$file}{revision}++;
+                    $head->{$file}{filehash} = "deleted";
+                    $head->{$file}{commithash} = $commit->{hash};
+                    $head->{$file}{modified} = $commit->{date};
+                    $head->{$file}{author} = $commit->{author};
+
+                    $db_insert_rev->execute($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+                }
+            }
+            # END : "Detect deleted files"
+        }
+
+
+        if (exists $commit->{mergemsg})
+        {
+            $db_insert_mergelog->execute($commit->{hash}, $commit->{mergemsg});
+        }
+
+        $lastpicked = $commit->{hash};
+
+        $self->_set_prop("last_commit", $commit->{hash});
+    }
+
+    $db_delete_head->execute();
+    foreach my $file ( keys %$head )
+    {
+        $db_insert_head->execute(
+            $file,
+            $head->{$file}{revision},
+            $head->{$file}{filehash},
+            $head->{$file}{commithash},
+            $head->{$file}{modified},
+            $head->{$file}{author},
+            $head->{$file}{mode},
+        );
+    }
+    # invalidate the gethead cache
+    $self->{gethead_cache} = undef;
+
+
+    # Ending exclusive lock here
+    $self->{dbh}->commit() or die "Failed to commit changes to SQLite";
+}
+
+sub _headrev
+{
+    my $self = shift;
+    my $filename = shift;
+
+    my $db_query = $self->{dbh}->prepare_cached("SELECT filehash, revision, mode FROM head WHERE name=?",{},1);
+    $db_query->execute($filename);
+    my ( $hash, $revision, $mode ) = $db_query->fetchrow_array;
+
+    return ( $hash, $revision, $mode );
+}
+
+sub _get_prop
+{
+    my $self = shift;
+    my $key = shift;
+
+    my $db_query = $self->{dbh}->prepare_cached("SELECT value FROM properties WHERE key=?",{},1);
+    $db_query->execute($key);
+    my ( $value ) = $db_query->fetchrow_array;
+
+    return $value;
+}
+
+sub _set_prop
+{
+    my $self = shift;
+    my $key = shift;
+    my $value = shift;
+
+    my $db_query = $self->{dbh}->prepare_cached("UPDATE properties SET value=? WHERE key=?",{},1);
+    $db_query->execute($value, $key);
+
+    unless ( $db_query->rows )
+    {
+        $db_query = $self->{dbh}->prepare_cached("INSERT INTO properties (key, value) VALUES (?,?)",{},1);
+        $db_query->execute($key, $value);
+    }
+
+    return $value;
+}
+
+=head2 gethead
+
+=cut
+
+sub gethead
+{
+    my $self = shift;
+
+    return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) );
+
+    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head ORDER BY name ASC",{},1);
+    $db_query->execute();
+
+    my $tree = [];
+    while ( my $file = $db_query->fetchrow_hashref )
+    {
+        push @$tree, $file;
+    }
+
+    $self->{gethead_cache} = $tree;
+
+    return $tree;
+}
+
+=head2 getlog
+
+=cut
+
+sub getlog
+{
+    my $self = shift;
+    my $filename = shift;
+
+    my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, author, mode, revision, modified, commithash FROM revision WHERE name=? ORDER BY revision DESC",{},1);
+    $db_query->execute($filename);
+
+    my $tree = [];
+    while ( my $file = $db_query->fetchrow_hashref )
+    {
+        push @$tree, $file;
+    }
+
+    return $tree;
+}
+
+=head2 getmeta
+
+This function takes a filename (with path) argument and returns a hashref of
+metadata for that file.
+
+=cut
+
+sub getmeta
+{
+    my $self = shift;
+    my $filename = shift;
+    my $revision = shift;
+
+    my $db_query;
+    if ( defined($revision) and $revision =~ /^\d+$/ )
+    {
+        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM revision WHERE name=? AND revision=?",{},1);
+        $db_query->execute($filename, $revision);
+    }
+    elsif ( defined($revision) and $revision =~ /^[a-zA-Z0-9]{40}$/ )
+    {
+        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM revision WHERE name=? AND commithash=?",{},1);
+        $db_query->execute($filename, $revision);
+    } else {
+        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM head WHERE name=?",{},1);
+        $db_query->execute($filename);
+    }
+
+    return $db_query->fetchrow_hashref;
+}
+
+=head2 commitmessage
+
+this function takes a commithash and returns the commit message for that commit
+
+=cut
+sub commitmessage
+{
+    my $self = shift;
+    my $commithash = shift;
+
+    die("Need commithash") unless ( defined($commithash) and $commithash =~ /^[a-zA-Z0-9]{40}$/ );
+
+    my $db_query;
+    $db_query = $self->{dbh}->prepare_cached("SELECT value FROM commitmsgs WHERE key=?",{},1);
+    $db_query->execute($commithash);
+
+    my ( $message ) = $db_query->fetchrow_array;
+
+    if ( defined ( $message ) )
+    {
+        $message .= " " if ( $message =~ /\n$/ );
+        return $message;
+    }
+
+    my @lines = safe_pipe_capture("git-cat-file", "commit", $commithash);
+    shift @lines while ( $lines[0] =~ /\S/ );
+    $message = join("",@lines);
+    $message .= " " if ( $message =~ /\n$/ );
+    return $message;
+}
+
+=head2 gethistory
+
+This function takes a filename (with path) argument and returns an arrayofarrays
+containing revision,filehash,commithash ordered by revision descending
+
+=cut
+sub gethistory
+{
+    my $self = shift;
+    my $filename = shift;
+
+    my $db_query;
+    $db_query = $self->{dbh}->prepare_cached("SELECT revision, filehash, commithash FROM revision WHERE name=? ORDER BY revision DESC",{},1);
+    $db_query->execute($filename);
+
+    return $db_query->fetchall_arrayref;
+}
+
+=head2 gethistorydense
+
+This function takes a filename (with path) argument and returns an arrayofarrays
+containing revision,filehash,commithash ordered by revision descending.
+
+This version of gethistory skips deleted entries -- so it is useful for annotate.
+The 'dense' part is a reference to a '--dense' option available for git-rev-list
+and other git tools that depend on it.
+
+=cut
+sub gethistorydense
+{
+    my $self = shift;
+    my $filename = shift;
+
+    my $db_query;
+    $db_query = $self->{dbh}->prepare_cached("SELECT revision, filehash, commithash FROM revision WHERE name=? AND filehash!='deleted' ORDER BY revision DESC",{},1);
+    $db_query->execute($filename);
+
+    return $db_query->fetchall_arrayref;
+}
+
+=head2 in_array()
+
+from Array::PAT - mimics the in_array() function
+found in PHP. Yuck but works for small arrays.
+
+=cut
+sub in_array
+{
+    my ($check, @array) = @_;
+    my $retval = 0;
+    foreach my $test (@array){
+        if($check eq $test){
+            $retval =  1;
+        }
+    }
+    return $retval;
+}
+
+=head2 safe_pipe_capture
+
+an alternative to `command` that allows input to be passed as an array
+to work around shell problems with weird characters in arguments
+
+=cut
+sub safe_pipe_capture {
+
+    my @output;
+
+    if (my $pid = open my $child, '-|') {
+        @output = (<$child>);
+        close $child or die join(' ',@_).": $! $?";
+    } else {
+        exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+    }
+    return wantarray ? @output : join('',@output);
+}
+
+
+1;
diff --git a/git-fetch.sh b/git-fetch.sh
new file mode 100755 (executable)
index 0000000..48818f8
--- /dev/null
@@ -0,0 +1,429 @@
+#!/bin/sh
+#
+
+USAGE='<fetch-options> <repository> <refspec>...'
+. git-sh-setup
+. 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"
+
+LF='
+'
+IFS="$LF"
+
+no_tags=
+tags=
+append=
+force=
+verbose=
+update_head_ok=
+exec=
+upload_pack=
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -a|--a|--ap|--app|--appe|--appen|--append)
+               append=t
+               ;;
+       --upl|--uplo|--uploa|--upload|--upload-|--upload-p|\
+       --upload-pa|--upload-pac|--upload-pack)
+               shift
+               exec="--exec=$1" 
+               upload_pack="-u $1"
+               ;;
+       -f|--f|--fo|--for|--forc|--force)
+               force=t
+               ;;
+       -t|--t|--ta|--tag|--tags)
+               tags=t
+               ;;
+       -n|--n|--no|--no-|--no-t|--no-ta|--no-tag|--no-tags)
+               no_tags=t
+               ;;
+       -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
+       --update-he|--update-hea|--update-head|--update-head-|\
+       --update-head-o|--update-head-ok)
+               update_head_ok=t
+               ;;
+       -v|--verbose)
+               verbose=Yes
+               ;;
+       -k|--k|--ke|--kee|--keep)
+               keep=--keep
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+done
+
+case "$#" in
+0)
+       test -f "$GIT_DIR/branches/origin" ||
+               test -f "$GIT_DIR/remotes/origin" ||
+                       die "Where do you want to fetch from today?"
+       set origin ;;
+esac
+
+remote_nick="$1"
+remote=$(get_remote_url "$@")
+refs=
+rref=
+rsync_slurped_objects=
+
+if test "" = "$append"
+then
+       : >"$GIT_DIR/FETCH_HEAD"
+fi
+
+append_fetch_head () {
+    head_="$1"
+    remote_="$2"
+    remote_name_="$3"
+    remote_nick_="$4"
+    local_name_="$5"
+    case "$6" in
+    t) not_for_merge_='not-for-merge' ;;
+    '') not_for_merge_= ;;
+    esac
+
+    # remote-nick is the URL given on the command line (or a shorthand)
+    # remote-name is the $GIT_DIR relative refs/ path we computed
+    # for this refspec.
+
+    # the $note_ variable will be fed to git-fmt-merge-msg for further
+    # processing.
+    case "$remote_name_" in
+    HEAD)
+       note_= ;;
+    refs/heads/*)
+       note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')"
+       note_="branch '$note_' of " ;;
+    refs/tags/*)
+       note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
+       note_="tag '$note_' of " ;;
+    refs/remotes/*)
+       note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')"
+       note_="remote branch '$note_' of " ;;
+    *)
+       note_="$remote_name of " ;;
+    esac
+    remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') &&
+       remote_="$remote_1_"
+    note_="$note_$remote_"
+
+    # 2.6.11-tree tag would not be happy to be fed to resolve.
+    if git-cat-file commit "$head_" >/dev/null 2>&1
+    then
+       headc_=$(git-rev-parse --verify "$head_^0") || exit
+       echo "$headc_   $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
+       [ "$verbose" ] && echo >&2 "* committish: $head_"
+       [ "$verbose" ] && echo >&2 "  $note_"
+    else
+       echo "$head_    not-for-merge   $note_" >>"$GIT_DIR/FETCH_HEAD"
+       [ "$verbose" ] && echo >&2 "* non-commit: $head_"
+       [ "$verbose" ] && echo >&2 "  $note_"
+    fi
+    if test "$local_name_" != ""
+    then
+       # We are storing the head locally.  Make sure that it is
+       # a fast forward (aka "reverse push").
+       fast_forward_local "$local_name_" "$head_" "$note_"
+    fi
+}
+
+fast_forward_local () {
+    mkdir -p "$(dirname "$GIT_DIR/$1")"
+    case "$1" in
+    refs/tags/*)
+       # Tags need not be pointing at commits so there
+       # is no way to guarantee "fast-forward" anyway.
+       if test -f "$GIT_DIR/$1"
+       then
+               if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+               then
+                       [ "$verbose" ] && echo >&2 "* $1: same as $3"
+               else
+                       echo >&2 "* $1: updating with $3"
+               fi
+       else
+               echo >&2 "* $1: storing $3"
+       fi
+       git-update-ref "$1" "$2"
+       ;;
+
+    refs/heads/* | refs/remotes/*)
+       # $1 is the ref being updated.
+       # $2 is the new value for the ref.
+       local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
+       if test "$local"
+       then
+           # Require fast-forward.
+           mb=$(git-merge-base "$local" "$2") &&
+           case "$2,$mb" in
+           $local,*)
+               if test -n "$verbose"
+               then
+                       echo >&2 "* $1: same as $3"
+               fi
+               ;;
+           *,$local)
+               echo >&2 "* $1: fast forward to $3"
+               echo >&2 "  from $local to $2"
+               git-update-ref "$1" "$2" "$local"
+               ;;
+           *)
+               false
+               ;;
+           esac || {
+               echo >&2 "* $1: does not fast forward to $3;"
+               case ",$force,$single_force," in
+               *,t,*)
+                       echo >&2 "  forcing update."
+                       git-update-ref "$1" "$2" "$local"
+                       ;;
+               *)
+                       echo >&2 "  not updating."
+                       exit 1
+                       ;;
+               esac
+           }
+       else
+           echo >&2 "* $1: storing $3"
+           git-update-ref "$1" "$2"
+       fi
+       ;;
+    esac
+}
+
+case "$update_head_ok" in
+'')
+       orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+       ;;
+esac
+
+# If --tags (and later --heads or --all) is specified, then we are
+# not talking about defaults stored in Pull: line of remotes or
+# branches file, and just fetch those and refspecs explicitly given.
+# Otherwise we do what we always did.
+
+reflist=$(get_remote_refs_for_fetch "$@")
+if test "$tags"
+then
+       taglist=`IFS="  " &&
+                 git-ls-remote $upload_pack --tags "$remote" |
+                 while read sha1 name
+                 do
+                       case "$name" in
+                       *^*) continue ;;
+                       esac
+                       if git-check-ref-format "$name"
+                       then
+                           echo ".${name}:${name}"
+                       else
+                           echo >&2 "warning: tag ${name} ignored"
+                       fi
+                 done`
+       if test "$#" -gt 1
+       then
+               # remote URL plus explicit refspecs; we need to merge them.
+               reflist="$reflist$LF$taglist"
+       else
+               # No explicit refspecs; fetch tags only.
+               reflist=$taglist
+       fi
+fi
+
+fetch_main () {
+  reflist="$1"
+  refs=
+
+  for ref in $reflist
+  do
+      refs="$refs$LF$ref"
+
+      # These are relative path from $GIT_DIR, typically starting at refs/
+      # but may be HEAD
+      if expr "z$ref" : 'z\.' >/dev/null
+      then
+         not_for_merge=t
+         ref=$(expr "z$ref" : 'z\.\(.*\)')
+      else
+         not_for_merge=
+      fi
+      if expr "z$ref" : 'z+' >/dev/null
+      then
+         single_force=t
+         ref=$(expr "z$ref" : 'z+\(.*\)')
+      else
+         single_force=
+      fi
+      remote_name=$(expr "z$ref" : 'z\([^:]*\):')
+      local_name=$(expr "z$ref" : 'z[^:]*:\(.*\)')
+
+      rref="$rref$LF$remote_name"
+
+      # There are transports that can fetch only one head at a time...
+      case "$remote" in
+      http://* | https://*)
+         if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+             curl_extra_args="-k"
+         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 "$remote/$remote_name_quoted")
+           depth=$( expr \( $depth + 1 \) )
+         done
+         expr "z$head" : "z$_x40\$" >/dev/null ||
+             die "Failed to fetch $remote_name from $remote"
+         echo >&2 Fetching "$remote_name from $remote" using http
+         git-http-fetch -v -a "$head" "$remote/" || exit
+         ;;
+      rsync://*)
+         TMP_HEAD="$GIT_DIR/TMP_HEAD"
+         rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
+         head=$(git-rev-parse --verify TMP_HEAD)
+         rm -f "$TMP_HEAD"
+         test "$rsync_slurped_objects" || {
+             rsync -av --ignore-existing --exclude info \
+                 "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
+
+             # Look at objects/info/alternates for rsync -- http will
+             # support it natively and git native ones will do it on
+             # the remote end.  Not having that file is not a crime.
+             rsync -q "$remote/objects/info/alternates" \
+                 "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+                 rm -f "$GIT_DIR/TMP_ALT"
+             if test -f "$GIT_DIR/TMP_ALT"
+             then
+                 resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
+                 while read alt
+                 do
+                     case "$alt" in 'bad alternate: '*) die "$alt";; esac
+                     echo >&2 "Getting alternate: $alt"
+                     rsync -av --ignore-existing --exclude info \
+                     "$alt" "$GIT_OBJECT_DIRECTORY/" || exit
+                 done
+                 rm -f "$GIT_DIR/TMP_ALT"
+             fi
+             rsync_slurped_objects=t
+         }
+         ;;
+      *)
+         # We will do git native transport with just one call later.
+         continue ;;
+      esac
+
+      append_fetch_head "$head" "$remote" \
+         "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+
+  done
+
+  case "$remote" in
+  http://* | https://* | rsync://* )
+      ;; # we are already done.
+  *)
+    ( : subshell because we muck with IFS
+      IFS="    $LF"
+      (
+         git-fetch-pack $exec $keep --thin "$remote" $rref || echo failed "$remote"
+      ) |
+      while read sha1 remote_name
+      do
+         case "$sha1" in
+         failed)
+                 echo >&2 "Fetch failure: $remote"
+                 exit 1 ;;
+         esac
+         found=
+         single_force=
+         for ref in $refs
+         do
+             case "$ref" in
+             +$remote_name:*)
+                 single_force=t
+                 not_for_merge=
+                 found="$ref"
+                 break ;;
+             .+$remote_name:*)
+                 single_force=t
+                 not_for_merge=t
+                 found="$ref"
+                 break ;;
+             .$remote_name:*)
+                 not_for_merge=t
+                 found="$ref"
+                 break ;;
+             $remote_name:*)
+                 not_for_merge=
+                 found="$ref"
+                 break ;;
+             esac
+         done
+         local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
+         append_fetch_head "$sha1" "$remote" \
+                 "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+      done
+    ) || exit ;;
+  esac
+
+}
+
+fetch_main "$reflist"
+
+# automated tag following
+case "$no_tags$tags" in
+'')
+       case "$reflist" in
+       *:refs/*)
+               # effective only when we are following remote branch
+               # using local tracking branch.
+               taglist=$(IFS=" " &&
+               git-ls-remote $upload_pack --tags "$remote" |
+               sed -ne 's|^\([0-9a-f]*\)[      ]\(refs/tags/.*\)^{}$|\1 \2|p' |
+               while read sha1 name
+               do
+                       test -f "$GIT_DIR/$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}"
+               done)
+       esac
+       case "$taglist" in
+       '') ;;
+       ?*)
+               fetch_main "$taglist" ;;
+       esac
+esac
+
+# If the original head was empty (i.e. no "master" yet), or
+# if we were told not to worry, we do not have to check.
+case ",$update_head_ok,$orig_head," in
+*,, | t,* )
+       ;;
+*)
+       curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+       if test "$curr_head" != "$orig_head"
+       then
+               git-update-ref HEAD "$orig_head"
+               die "Cannot fetch into the current branch."
+       fi
+       ;;
+esac
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
new file mode 100755 (executable)
index 0000000..5986e54
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/perl -w
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Read .git/FETCH_HEAD and make a human readable merge message
+# by grouping branches and tags together to form a single line.
+
+use strict;
+
+my @src;
+my %src;
+sub andjoin {
+       my ($label, $labels, $stuff) = @_;
+       my $l = scalar @$stuff;
+       my $m = '';
+       if ($l == 0) {
+               return ();
+       }
+       if ($l == 1) {
+               $m = "$label$stuff->[0]";
+       }
+       else {
+               $m = ("$labels" .
+                     join (', ', @{$stuff}[0..$l-2]) .
+                     " and $stuff->[-1]");
+       }
+       return ($m);
+}
+
+sub repoconfig {
+       my ($val) = qx{git-repo-config --get merge.summary};
+       return $val;
+}
+
+sub current_branch {
+       my ($bra) = qx{git-symbolic-ref HEAD};
+       chomp($bra);
+       $bra =~ s|^refs/heads/||;
+       if ($bra ne 'master') {
+               $bra = " into $bra";
+       } else {
+               $bra = "";
+       }
+       return $bra;
+}
+
+sub shortlog {
+       my ($tip) = @_;
+       my @result;
+       foreach ( qx{git-log --no-merges --topo-order --pretty=oneline $tip ^HEAD} ) {
+               s/^[0-9a-f]{40}\s+//;
+               push @result, $_;
+       }
+       die "git-log failed\n" if $?;
+       return @result;
+}
+
+my @origin = ();
+while (<>) {
+       my ($bname, $tname, $gname, $src, $sha1, $origin);
+       chomp;
+       s/^([0-9a-f]*)  //;
+       $sha1 = $1;
+       next if (/^not-for-merge/);
+       s/^     //;
+       if (s/ of (.*)$//) {
+               $src = $1;
+       } else {
+               # Pulling HEAD
+               $src = $_;
+               $_ = 'HEAD';
+       }
+       if (! exists $src{$src}) {
+               push @src, $src;
+               $src{$src} = {
+                       BRANCH => [],
+                       TAG => [],
+                       R_BRANCH => [],
+                       GENERIC => [],
+                       # &1 == has HEAD.
+                       # &2 == has others.
+                       HEAD_STATUS => 0,
+               };
+       }
+       if (/^branch (.*)$/) {
+               $origin = $1;
+               push @{$src{$src}{BRANCH}}, $1;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+       elsif (/^tag (.*)$/) {
+               $origin = $_;
+               push @{$src{$src}{TAG}}, $1;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+       elsif (/^remote branch (.*)$/) {
+               $origin = $1;
+               push @{$src{$src}{R_BRANCH}}, $1;
+               $src{$src}{HEAD_STATUS} |= 2;
+       }
+       elsif (/^HEAD$/) {
+               $origin = $src;
+               $src{$src}{HEAD_STATUS} |= 1;
+       }
+       else {
+               push @{$src{$src}{GENERIC}}, $_;
+               $src{$src}{HEAD_STATUS} |= 2;
+               $origin = $src;
+       }
+       if ($src eq '.' || $src eq $origin) {
+               $origin =~ s/^'(.*)'$/$1/;
+               push @origin, [$sha1, "$origin"];
+       }
+       else {
+               push @origin, [$sha1, "$origin of $src"];
+       }
+}
+
+my @msg;
+for my $src (@src) {
+       if ($src{$src}{HEAD_STATUS} == 1) {
+               # Only HEAD is fetched, nothing else.
+               push @msg, $src;
+               next;
+       }
+       my @this;
+       if ($src{$src}{HEAD_STATUS} == 3) {
+               # HEAD is fetched among others.
+               push @this, andjoin('', '', ['HEAD']);
+       }
+       push @this, andjoin("branch ", "branches ",
+                          $src{$src}{BRANCH});
+       push @this, andjoin("remote branch ", "remote branches ",
+                          $src{$src}{R_BRANCH});
+       push @this, andjoin("tag ", "tags ",
+                          $src{$src}{TAG});
+       push @this, andjoin("commit ", "commits ",
+                           $src{$src}{GENERIC});
+       my $this = join(', ', @this);
+       if ($src ne '.') {
+               $this .= " of $src";
+       }
+       push @msg, $this;
+}
+
+my $into = current_branch();
+
+print "Merge ", join("; ", @msg), $into, "\n";
+
+if (!repoconfig) {
+       exit(0);
+}
+
+# We limit the merge message to the latst 20 or so per each branch.
+my $limit = 20;
+
+for (@origin) {
+       my ($sha1, $name) = @$_;
+       my @log = shortlog($sha1);
+       if ($limit + 1 <= @log) {
+               print "\n* $name: (" . scalar(@log) . " commits)\n";
+       }
+       else {
+               print "\n* $name:\n";
+       }
+       my $cnt = 0;
+       for my $log (@log) {
+               if ($limit < ++$cnt) {
+                       print "  ...\n";
+                       last;
+               }
+               print "  $log";
+       }
+}
diff --git a/git-lost-found.sh b/git-lost-found.sh
new file mode 100755 (executable)
index 0000000..ba6d587
--- /dev/null
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+USAGE=''
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+if [ "$#" != "0" ]
+then
+    usage
+fi
+
+laf="$GIT_DIR/lost-found"
+rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
+
+git fsck-objects |
+while read dangling type sha1
+do
+       case "$dangling" in
+       dangling)
+               if git-rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
+               then
+                       dir="$laf/commit"
+                       git-show-branch "$sha1"
+               else
+                       dir="$laf/other"
+               fi
+               echo "$sha1" >"$dir/$sha1"
+               ;;
+       esac
+done
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
new file mode 100755 (executable)
index 0000000..2fdcaf7
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+
+usage () {
+    echo >&2 "usage: $0 [--heads] [--tags] [-u|--upload-pack <upload-pack>]"
+    echo >&2 "          <repository> <refs>..."
+    exit 1;
+}
+
+die () {
+    echo >&2 "$*"
+    exit 1
+}
+
+exec=
+while case "$#" in 0) break;; esac
+do
+  case "$1" in
+  -h|--h|--he|--hea|--head|--heads)
+  heads=heads; shift ;;
+  -t|--t|--ta|--tag|--tags)
+  tags=tags; shift ;;
+  -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
+  --upload-pac|--upload-pack)
+       shift
+       exec="--exec=$1"
+       shift;;
+  --)
+  shift; break ;;
+  -*)
+  usage ;;
+  *)
+  break ;;
+  esac
+done
+
+case "$#" in 0) usage ;; esac
+
+case ",$heads,$tags," in
+,,,) heads=heads tags=tags other=other ;;
+esac
+
+. git-parse-remote
+peek_repo="$(get_remote_url "$@")"
+shift
+
+tmp=.ls-remote-$$
+trap "rm -fr $tmp-*" 0 1 2 3 15
+tmpdir=$tmp-d
+
+case "$peek_repo" in
+http://* | https://* )
+        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+            curl_extra_args="-k"
+        fi
+       curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
+               echo "failed    slurping"
+       ;;
+
+rsync://* )
+       mkdir $tmpdir &&
+       rsync -rlq "$peek_repo/HEAD" $tmpdir &&
+       rsync -rq "$peek_repo/refs" $tmpdir || {
+               echo "failed    slurping"
+               exit
+       }
+       head=$(cat "$tmpdir/HEAD") &&
+       case "$head" in
+       ref:' '*)
+               head=$(expr "z$head" : 'zref: \(.*\)') &&
+               head=$(cat "$tmpdir/$head") || exit
+       esac &&
+       echo "$head     HEAD"
+       (cd $tmpdir && find refs -type f) |
+       while read path
+       do
+               cat "$tmpdir/$path" | tr -d '\012'
+               echo "  $path"
+       done &&
+       rm -fr $tmpdir
+       ;;
+
+* )
+       git-peek-remote $exec "$peek_repo" ||
+               echo "failed    slurping"
+       ;;
+esac |
+sort -t '      ' -k 2 |
+while read sha1 path
+do
+       case "$sha1" in
+       failed)
+               die "Failed to find remote refs"
+       esac
+       case "$path" in
+       refs/heads/*)
+               group=heads ;;
+       refs/tags/*)
+               group=tags ;;
+       *)
+               group=other ;;
+       esac
+       case ",$heads,$tags,$other," in
+       *,$group,*)
+               ;;
+       *)
+               continue;;
+       esac
+       case "$#" in
+       0)
+               match=yes ;;
+       *)
+               match=no
+               for pat
+               do
+                       case "/$path" in
+                       */$pat )
+                               match=yes
+                               break ;;
+                       esac
+               done
+       esac
+       case "$match" in
+       no)
+               continue ;;
+       esac
+       echo "$sha1     $path"
+done
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
new file mode 100755 (executable)
index 0000000..eb3f473
--- /dev/null
@@ -0,0 +1,114 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Resolve two or more trees.
+#
+
+LF='
+'
+
+die () {
+    echo >&2 "$*"
+    exit 1
+}
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+       case ",$sep_seen,$head,$arg," in
+       *,--,)
+               sep_seen=yes
+               ;;
+       ,yes,,*)
+               head=$arg
+               ;;
+       ,yes,*)
+               remotes="$remotes$arg "
+               ;;
+       *)
+               bases="$bases$arg "
+               ;;
+       esac
+done
+
+# Reject if this is not an Octopus -- resolve should be used instead.
+case "$remotes" in
+?*' '?*)
+       ;;
+*)
+       exit 2 ;;
+esac
+
+# MRC is the current "merge reference commit"
+# MRT is the current "merge result tree"
+
+MRC=$head MSG= PARENT="-p $head"
+MRT=$(git-write-tree)
+CNT=1 ;# counting our head
+NON_FF_MERGE=0
+OCTOPUS_FAILURE=0
+for SHA1 in $remotes
+do
+       case "$OCTOPUS_FAILURE" in
+       1)
+               # We allow only last one to have a hand-resolvable
+               # conflicts.  Last round failed and we still had
+               # a head to merge.
+               echo "Automated merge did not work."
+               echo "Should not be doing an Octopus."
+               exit 2
+       esac
+
+       common=$(git-merge-base --all $MRC $SHA1) ||
+               die "Unable to find common commit with $SHA1"
+
+       case "$LF$common$LF" in
+       *"$LF$SHA1$LF"*)
+               echo "Already up-to-date with $SHA1"
+               continue
+               ;;
+       esac
+
+       CNT=`expr $CNT + 1`
+       PARENT="$PARENT -p $SHA1"
+
+       if test "$common,$NON_FF_MERGE" = "$MRC,0"
+       then
+               # The first head being merged was a fast-forward.
+               # Advance MRC to the head being merged, and use that
+               # tree as the intermediate result of the merge.
+               # We still need to count this as part of the parent set.
+
+               echo "Fast forwarding to: $SHA1"
+               git-read-tree -u -m $head $SHA1 || exit
+               MRC=$SHA1 MRT=$(git-write-tree)
+               continue
+       fi
+
+       NON_FF_MERGE=1
+
+       echo "Trying simple merge with $SHA1"
+       git-read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2
+       next=$(git-write-tree 2>/dev/null)
+       if test $? -ne 0
+       then
+               echo "Simple merge did not work, trying automatic merge."
+               git-merge-index -o git-merge-one-file -a ||
+               OCTOPUS_FAILURE=1
+               next=$(git-write-tree 2>/dev/null)
+       fi
+
+       # We have merged the other branch successfully.  Ideally
+       # we could implement OR'ed heads in merge-base, and keep
+       # a list of commits we have merged so far in MRC to feed
+       # them to merge-base, but we approximate it by keep using
+       # the current MRC.  We used to update it to $common, which
+       # was incorrectly doing AND'ed merge-base here, which was
+       # unneeded.
+
+       MRT=$next
+done
+
+exit "$OCTOPUS_FAILURE"
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
new file mode 100755 (executable)
index 0000000..5619409
--- /dev/null
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# Copyright (c) Linus Torvalds, 2005
+#
+# This is the git per-file merge script, called with
+#
+#   $1 - original file SHA1 (or empty)
+#   $2 - file in branch1 SHA1 (or empty)
+#   $3 - file in branch2 SHA1 (or empty)
+#   $4 - pathname in repository
+#   $5 - orignal file mode (or empty)
+#   $6 - file in branch1 mode (or empty)
+#   $7 - file in branch2 mode (or empty)
+#
+# Handle some trivial cases.. The _really_ trivial cases have
+# been handled already by git-read-tree, but that one doesn't
+# do any merges that might change the tree layout.
+
+case "${1:-.}${2:-.}${3:-.}" in
+#
+# Deleted in both or deleted in one and unchanged in the other
+#
+"$1.." | "$1.$1" | "$1$1.")
+       if [ "$2" ]; then
+               echo "Removing $4"
+       fi
+       if test -f "$4"; then
+               rm -f -- "$4" &&
+               rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
+       fi &&
+               exec git-update-index --remove -- "$4"
+       ;;
+
+#
+# Added in one.
+#
+".$2." | "..$3" )
+       echo "Adding $4"
+       git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
+               exec git-checkout-index -u -f -- "$4"
+       ;;
+
+#
+# Added in both, identically (check for same permissions).
+#
+".$3$2")
+       if [ "$6" != "$7" ]; then
+               echo "ERROR: File $4 added identically in both branches,"
+               echo "ERROR: but permissions conflict $6->$7."
+               exit 1
+       fi
+       echo "Adding $4"
+       git-update-index --add --cacheinfo "$6" "$2" "$4" &&
+               exec git-checkout-index -u -f -- "$4"
+       ;;
+
+#
+# Modified in both, but differently.
+#
+"$1$2$3" | ".$2$3")
+
+       case ",$6,$7," in
+       *,120000,*)
+               echo "ERROR: $4: Not merging symbolic link changes."
+               exit 1
+               ;;
+       esac
+
+       src2=`git-unpack-file $3`
+       case "$1" in
+       '')
+               echo "Added $4 in both, but differently."
+               # This extracts OUR file in $orig, and uses git-apply to
+               # remove lines that are unique to ours.
+               orig=`git-unpack-file $2`
+               sz0=`wc -c <"$orig"`
+               diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add 
+               sz1=`wc -c <"$orig"`
+
+               # If we do not have enough common material, it is not
+               # worth trying two-file merge using common subsections.
+               expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig
+               ;;
+       *)
+               echo "Auto-merging $4"
+               orig=`git-unpack-file $1`
+               ;;
+       esac
+
+       # Be careful for funny filename such as "-L" in "$4", which
+       # would confuse "merge" greatly.
+       src1=`git-unpack-file $2`
+       merge "$src1" "$orig" "$src2"
+       ret=$?
+
+       # Create the working tree file, using "our tree" version from the
+       # index, and then store the result of the merge.
+       git-checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
+       rm -f -- "$orig" "$src1" "$src2"
+
+       if [ "$6" != "$7" ]; then
+               echo "ERROR: Permissions conflict: $5->$6,$7."
+               ret=1
+       fi
+       if [ "$1" = '' ]; then
+               ret=1
+       fi
+
+       if [ $ret -ne 0 ]; then
+               echo "ERROR: Merge conflict in $4"
+               exit 1
+       fi
+       exec git-update-index -- "$4"
+       ;;
+
+*)
+       echo "ERROR: $4: Not handling case $1 -> $2 -> $3"
+       ;;
+esac
+exit 1
diff --git a/git-merge-ours.sh b/git-merge-ours.sh
new file mode 100755 (executable)
index 0000000..4f3d053
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Pretend we resolved the heads, but declare our tree trumps everybody else.
+#
+
+# We need to exit with 2 if the index does not match our HEAD tree,
+# because the current index is what we will be committing as the
+# merge result.
+
+test "$(git-diff-index --cached --name-status HEAD)" = "" || exit 2
+
+exit 0
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
new file mode 100755 (executable)
index 0000000..ce8a31f
--- /dev/null
@@ -0,0 +1,944 @@
+#!/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 cleaness 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)
diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh
new file mode 100755 (executable)
index 0000000..0a8ef21
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
+# Resolve two trees, using enhancd multi-base read-tree.
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+       case ",$sep_seen,$head,$arg," in
+       *,--,)
+               sep_seen=yes
+               ;;
+       ,yes,,*)
+               head=$arg
+               ;;
+       ,yes,*)
+               remotes="$remotes$arg "
+               ;;
+       *)
+               bases="$bases$arg "
+               ;;
+       esac
+done
+
+# Give up if we are given more than two remotes -- not handling octopus.
+case "$remotes" in
+?*' '?*)
+       exit 2 ;;
+esac
+
+# Give up if this is a baseless merge.
+if test '' = "$bases"
+then
+       exit 2
+fi
+
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m --aggressive $bases $head $remotes || exit 2
+echo "Trying simple merge."
+if result_tree=$(git-write-tree  2>/dev/null)
+then
+       exit 0
+else
+       echo "Simple merge failed, trying Automatic merge."
+       if git-merge-index -o git-merge-one-file -a
+       then
+               exit 0
+       else
+               exit 1
+       fi
+fi
diff --git a/git-merge-stupid.sh b/git-merge-stupid.sh
new file mode 100755 (executable)
index 0000000..4faecb9
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees, 'stupid merge'.
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+       case ",$sep_seen,$head,$arg," in
+       *,--,)
+               sep_seen=yes
+               ;;
+       ,yes,,*)
+               head=$arg
+               ;;
+       ,yes,*)
+               remotes="$remotes$arg "
+               ;;
+       *)
+               bases="$bases$arg "
+               ;;
+       esac
+done
+
+# Give up if we are given more than two remotes -- not handling octopus.
+case "$remotes" in
+?*' '?*)
+       exit 2 ;;
+esac
+
+# Find an optimum merge base if there are more than one candidates.
+case "$bases" in
+?*' '?*)
+       echo "Trying to find the optimum merge base."
+       G=.tmp-index$$
+       best=
+       best_cnt=-1
+       for c in $bases
+       do
+               rm -f $G
+               GIT_INDEX_FILE=$G git-read-tree -m $c $head $remotes \
+                        2>/dev/null || continue
+               # Count the paths that are unmerged.
+               cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+               if test $best_cnt -le 0 -o $cnt -le $best_cnt
+               then
+                       best=$c
+                       best_cnt=$cnt
+                       if test "$best_cnt" -eq 0
+                       then
+                               # Cannot do any better than all trivial merge.
+                               break
+                       fi
+               fi
+       done
+       rm -f $G
+       common="$best"
+       ;;
+*)
+       common="$bases"
+       ;;
+esac
+
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m $common $head $remotes || exit 2
+echo "Trying simple merge."
+if result_tree=$(git-write-tree  2>/dev/null)
+then
+       exit 0
+else
+       echo "Simple merge failed, trying Automatic merge."
+       if git-merge-index -o git-merge-one-file -a
+       then
+               exit 0
+       else
+               exit 1
+       fi
+fi
diff --git a/git-merge.sh b/git-merge.sh
new file mode 100755 (executable)
index 0000000..af1f25b
--- /dev/null
@@ -0,0 +1,338 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+
+USAGE='[-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+'
+. git-sh-setup
+
+LF='
+'
+
+all_strategies='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='resolve octopus stupid ours'
+       default_twohead_strategies='resolve'
+fi
+
+dropsave() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+                "$GIT_DIR/MERGE_SAVE" || exit 1
+}
+
+savestate() {
+       # Stash away any local modifications.
+       git-diff-index -z --name-only $head |
+       cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
+}
+
+restorestate() {
+        if test -f "$GIT_DIR/MERGE_SAVE"
+       then
+               git reset --hard $head
+               cpio -iuv <"$GIT_DIR/MERGE_SAVE"
+               git-update-index --refresh >/dev/null
+       fi
+}
+
+finish () {
+       test '' = "$2" || echo "$2"
+       case "$merge_msg" in
+       '')
+               echo "No merge message -- not updating HEAD"
+               ;;
+       *)
+               git-update-ref HEAD "$1" "$head" || exit 1
+               ;;
+       esac
+
+       case "$no_summary" in
+       '')
+               git-diff-tree -p --stat --summary -M "$head" "$1"
+               ;;
+       esac
+}
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
+               --no-summa|--no-summar|--no-summary)
+               no_summary=t ;;
+       --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+               no_commit=t ;;
+       -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+               --strateg=*|--strategy=*|\
+       -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+               case "$#,$1" in
+               *,*=*)
+                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       strategy="$2"
+                       shift ;;
+               esac
+               case " $all_strategies " in
+               *" $strategy "*)
+                       use_strategies="$use_strategies$strategy " ;;
+               *)
+                       die "available strategies are: $all_strategies" ;;
+               esac
+               ;;
+       -*)     usage ;;
+       *)      break ;;
+       esac
+       shift
+done
+
+merge_msg="$1"
+shift
+head_arg="$1"
+head=$(git-rev-parse --verify "$1"^0) || usage
+shift
+
+# All the rest are remote heads
+test "$#" = 0 && usage ;# we need at least one remote head.
+
+remoteheads=
+for remote
+do
+       remotehead=$(git-rev-parse --verify "$remote"^0) ||
+           die "$remote - not something we can merge"
+       remoteheads="${remoteheads}$remotehead "
+done
+set x $remoteheads ; shift
+
+case "$use_strategies" in
+'')
+       case "$#" in
+       1)
+               use_strategies="$default_twohead_strategies" ;;
+       *)
+               use_strategies="$default_octopus_strategies" ;;
+       esac
+       ;;
+esac
+
+for s in $use_strategies
+do
+       case " $s " in
+       *" $no_trivial_merge_strategies "*)
+               index_merge=f
+               break
+               ;;
+       esac
+done
+
+case "$#" in
+1)
+       common=$(git-merge-base --all $head "$@")
+       ;;
+*)
+       common=$(git-show-branch --merge-base $head "$@")
+       ;;
+esac
+echo "$head" >"$GIT_DIR/ORIG_HEAD"
+
+case "$index_merge,$#,$common,$no_commit" in
+f,*)
+       # We've been told not to try anything clever.  Skip to real merge.
+       ;;
+?,*,'',*)
+       # No common ancestors found. We need a real merge.
+       ;;
+?,1,"$1",*)
+       # If head can reach all the merge then we are up to date.
+       # but first the most common case of merging one remote.
+       echo "Already up-to-date."
+       dropsave
+       exit 0
+       ;;
+?,1,"$head",*)
+       # Again the most common case of merging one remote.
+       echo "Updating from $head to $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" &&
+       finish "$new_head" "Fast forward"
+       dropsave
+       exit 0
+       ;;
+?,1,?*"$LF"?*,*)
+       # We are not doing octopus and not fast forward.  Need a
+       # real merge.
+       ;;
+?,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..."
+       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."
+       ;;
+*)
+       # An octopus.  If we can reach all the remote we are up to date.
+       up_to_date=t
+       for remote
+       do
+               common_one=$(git-merge-base --all $head $remote)
+               if test "$common_one" != "$remote"
+               then
+                       up_to_date=f
+                       break
+               fi
+       done
+       if test "$up_to_date" = t
+       then
+               echo "Already up-to-date. Yeeah!"
+               dropsave
+               exit 0
+       fi
+       ;;
+esac
+
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+# At this point, we need a real merge.  No matter what strategy
+# we use, it would operate on the index, possibly affecting the
+# working tree, and when resolved cleanly, have the desired tree
+# in the index -- this means that the index must be in sync with
+# the $head commit.  The strategies are responsible to ensure this.
+
+case "$use_strategies" in
+?*' '?*)
+    # Stash away the local changes so that we can try more than one.
+    savestate
+    single_strategy=no
+    ;;
+*)
+    rm -f "$GIT_DIR/MERGE_SAVE"
+    single_strategy=yes
+    ;;
+esac
+
+result_tree= best_cnt=-1 best_strategy= wt_strategy=
+merge_was_ok=
+for strategy in $use_strategies
+do
+    test "$wt_strategy" = '' || {
+       echo "Rewinding the tree to pristine..."
+       restorestate
+    }
+    case "$single_strategy" in
+    no)
+       echo "Trying merge strategy $strategy..."
+       ;;
+    esac
+
+    # Remember which strategy left the state in the working tree
+    wt_strategy=$strategy
+
+    git-merge-$strategy $common -- "$head_arg" "$@"
+    exit=$?
+    if test "$no_commit" = t && test "$exit" = 0
+    then
+        merge_was_ok=t
+       exit=1 ;# pretend it left conflicts.
+    fi
+
+    test "$exit" = 0 || {
+
+       # The backend exits with 1 when conflicts are left to be resolved,
+       # with 2 when it does not handle the given merge at all.
+
+       if test "$exit" -eq 1
+       then
+           cnt=`{
+               git-diff-files --name-only
+               git-ls-files --unmerged
+           } | wc -l`
+           if test $best_cnt -le 0 -o $cnt -le $best_cnt
+           then
+               best_strategy=$strategy
+               best_cnt=$cnt
+           fi
+       fi
+       continue
+    }
+
+    # Automerge succeeded.
+    result_tree=$(git-write-tree) && break
+done
+
+# If we have a resulting tree, that means the strategy module
+# auto resolved the merge cleanly.
+if test '' != "$result_tree"
+then
+    parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
+    result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
+    finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
+    dropsave
+    exit 0
+fi
+
+# Pick the result from the best strategy and have the user fix it up.
+case "$best_strategy" in
+'')
+       restorestate
+       echo >&2 "No merge strategy handled the merge."
+       exit 2
+       ;;
+"$wt_strategy")
+       # We already have its result in the working tree.
+       ;;
+*)
+       echo "Rewinding the tree to pristine..."
+       restorestate
+       echo "Using the $best_strategy to prepare resolving by hand."
+       git-merge-$best_strategy $common -- "$head_arg" "$@"
+       ;;
+esac
+for remote
+do
+       echo $remote
+done >"$GIT_DIR/MERGE_HEAD"
+echo "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+
+if test "$merge_was_ok" = t
+then
+       echo >&2 \
+       "Automatic merge went well; stopped before committing as requested"
+       exit 0
+else
+       {
+           echo '
+Conflicts:
+'
+               git ls-files --unmerged |
+               sed -e 's/^[^   ]*      /       /' |
+               uniq
+       } >>"$GIT_DIR/MERGE_MSG"
+       if test -d "$GIT_DIR/rr-cache"
+       then
+               git-rerere
+       fi
+       die "Automatic merge failed; fix conflicts and then commit the result."
+fi
diff --git a/git-mv.perl b/git-mv.perl
new file mode 100755 (executable)
index 0000000..75aa8fe
--- /dev/null
@@ -0,0 +1,250 @@
+#!/usr/bin/perl
+#
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#                 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+
+use warnings;
+use strict;
+use Getopt::Std;
+
+sub usage() {
+       print <<EOT;
+$0 [-f] [-n] <source> <destination>
+$0 [-f] [-n] [-k] <source> ... <destination directory>
+EOT
+       exit(1);
+}
+
+our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
+getopts("hnfkv") || usage;
+usage() if $opt_h;
+@ARGV >= 1 or usage;
+
+my $GIT_DIR = `git rev-parse --git-dir`;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
+my (@srcArgs, @dstArgs, @srcs, @dsts);
+my ($src, $dst, $base, $dstDir);
+
+# remove any trailing slash in arguments
+for (@ARGV) { s/\/*$//; }
+
+my $argCount = scalar @ARGV;
+if (-d $ARGV[$argCount-1]) {
+       $dstDir = $ARGV[$argCount-1];
+       @srcArgs = @ARGV[0..$argCount-2];
+
+       foreach $src (@srcArgs) {
+               $base = $src;
+               $base =~ s/^.*\///;
+               $dst = "$dstDir/". $base;
+               push @dstArgs, $dst;
+       }
+}
+else {
+    if ($argCount < 2) {
+       print "Error: need at least two arguments\n";
+       exit(1);
+    }
+    if ($argCount > 2) {
+       print "Error: moving to directory '"
+           . $ARGV[$argCount-1]
+           . "' not possible; not existing\n";
+       exit(1);
+    }
+    @srcArgs = ($ARGV[0]);
+    @dstArgs = ($ARGV[1]);
+    $dstDir = "";
+}
+
+my $subdir_prefix = `git rev-parse --show-prefix`;
+chomp($subdir_prefix);
+
+# run in git base directory, so that git-ls-files lists all revisioned files
+chdir "$GIT_DIR/..";
+
+# normalize paths, needed to compare against versioned files and update-index
+# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+for (@srcArgs, @dstArgs) {
+    # prepend git prefix as we run from base directory
+    $_ = $subdir_prefix.$_;
+    s|^\./||;
+    s|/\./|/| while (m|/\./|);
+    s|//+|/|g;
+    # Also "a/b/../c" ==> "a/c"
+    1 while (s,(^|/)[^/]+/\.\./,$1,);
+}
+
+my (@allfiles,@srcfiles,@dstfiles);
+my $safesrc;
+my (%overwritten, %srcForDst);
+
+$/ = "\0";
+open(F, 'git-ls-files -z |')
+        or die "Failed to open pipe from git-ls-files: " . $!;
+
+@allfiles = map { chomp; $_; } <F>;
+close(F);
+
+
+my ($i, $bad);
+while(scalar @srcArgs > 0) {
+    $src = shift @srcArgs;
+    $dst = shift @dstArgs;
+    $bad = "";
+
+    for ($src, $dst) {
+       # Be nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+       s|^\./||;
+       s|/\./|/| while (m|/\./|);
+       s|//+|/|g;
+       # Also "a/b/../c" ==> "a/c"
+       1 while (s,(^|/)[^/]+/\.\./,$1,);
+    }
+
+    if ($opt_v) {
+       print "Checking rename of '$src' to '$dst'\n";
+    }
+
+    unless (-f $src || -l $src || -d $src) {
+       $bad = "bad source '$src'";
+    }
+
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+
+    $overwritten{$dst} = 0;
+    if (($bad eq "") && -e $dst) {
+       $bad = "destination '$dst' already exists";
+       if ($opt_f) {
+           # only files can overwrite each other: check both source and destination
+           if (-f $dst && (scalar @srcfiles == 1)) {
+               print "Warning: $bad; will overwrite!\n";
+               $bad = "";
+               $overwritten{$dst} = 1;
+           }
+           else {
+               $bad = "Can not overwrite '$src' with '$dst'";
+           }
+       }
+    }
+    
+    if (($bad eq "") && ($dst =~ /^$safesrc\//)) {
+       $bad = "can not move directory '$src' into itself";
+    }
+
+    if ($bad eq "") {
+        if (scalar @srcfiles == 0) {
+           $bad = "'$src' not under version control";
+       }
+    }
+
+    if ($bad eq "") {
+       if (defined $srcForDst{$dst}) {
+           $bad = "can not move '$src' to '$dst'; already target of ";
+           $bad .= "'".$srcForDst{$dst}."'";
+       }
+       else {
+           $srcForDst{$dst} = $src;
+       }
+    }
+
+    if ($bad ne "") {
+       if ($opt_k) {
+           print "Warning: $bad; skipping\n";
+           next;
+       }
+       print "Error: $bad\n";
+       exit(1);
+    }
+    push @srcs, $src;
+    push @dsts, $dst;
+}
+
+# Final pass: rename/move
+my (@deletedfiles,@addedfiles,@changedfiles);
+$bad = "";
+while(scalar @srcs > 0) {
+    $src = shift @srcs;
+    $dst = shift @dsts;
+
+    if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
+    if (!$opt_n) {
+       if (!rename($src,$dst)) {
+           $bad = "renaming '$src' failed: $!";
+           if ($opt_k) {
+               print "Warning: skipped: $bad\n";
+               $bad = "";
+               next;
+           }
+           last;
+       }
+    }
+
+    $safesrc = quotemeta($src);
+    @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+    @dstfiles = @srcfiles;
+    s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
+
+    push @deletedfiles, @srcfiles;
+    if (scalar @srcfiles == 1) {
+       # $dst can be a directory with 1 file inside
+       if ($overwritten{$dst} ==1) {
+           push @changedfiles, $dstfiles[0];
+
+       } else {
+           push @addedfiles, $dstfiles[0];
+       }
+    }
+    else {
+       push @addedfiles, @dstfiles;
+    }
+}
+
+if ($opt_n) {
+    if (@changedfiles) {
+       print "Changed  : ". join(", ", @changedfiles) ."\n";
+    }
+    if (@addedfiles) {
+       print "Adding   : ". join(", ", @addedfiles) ."\n";
+    }
+    if (@deletedfiles) {
+       print "Deleting : ". join(", ", @deletedfiles) ."\n";
+    }
+}
+else {
+    if (@changedfiles) {
+       open(H, "| git-update-index -z --stdin")
+               or die "git-update-index failed to update changed files with code $!\n";
+       foreach my $fileName (@changedfiles) {
+               print H "$fileName\0";
+       }
+       close(H);
+    }
+    if (@addedfiles) {
+       open(H, "| git-update-index --add -z --stdin")
+               or die "git-update-index failed to add new names with code $!\n";
+       foreach my $fileName (@addedfiles) {
+               print H "$fileName\0";
+       }
+       close(H);
+    }
+    if (@deletedfiles) {
+       open(H, "| git-update-index --remove -z --stdin")
+               or die "git-update-index failed to remove old names with code $!\n";
+       foreach my $fileName (@deletedfiles) {
+               print H "$fileName\0";
+       }
+       close(H);
+    }
+}
+
+if ($bad ne "") {
+    print "Error: $bad\n";
+    exit(1);
+}
diff --git a/git-p4import.py b/git-p4import.py
new file mode 100644 (file)
index 0000000..74172ab
--- /dev/null
@@ -0,0 +1,357 @@
+#!/usr/bin/python
+#
+# This tool is copyright (c) 2006, Sean Estabrooks.
+# It is released under the Gnu Public License, version 2.
+#
+# Import Perforce branches into Git repositories.
+# Checking out the files is done by calling the standard p4
+# client which you must have properly configured yourself
+#
+
+import marshal
+import os
+import sys
+import time
+import getopt
+
+from signal import signal, \
+   SIGPIPE, SIGINT, SIG_DFL, \
+   default_int_handler
+
+signal(SIGPIPE, SIG_DFL)
+s = signal(SIGINT, SIG_DFL)
+if s != default_int_handler:
+   signal(SIGINT, s)
+
+
+def die(msg, *args):
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    print "git-p4import fatal error:", msg
+    sys.exit(1)
+
+def usage():
+    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
+    sys.exit(1)
+
+verbosity = 1
+logfile = "/dev/null"
+ignore_warnings = False
+stitch = 0
+
+def report(level, msg, *args):
+    global verbosity
+    global logfile
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    fd = open(logfile, "a")
+    fd.writelines(msg)
+    fd.close()
+    if level <= verbosity:
+        print msg
+
+class p4_command:
+    def __init__(self, _repopath):
+        try:
+            global logfile
+            self.userlist = {}
+            if _repopath[-1] == '/':
+                self.repopath = _repopath[:-1]
+            else:
+                self.repopath = _repopath
+            if self.repopath[-4:] != "/...":
+                self.repopath= "%s/..." % self.repopath
+            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
+            a = f.readlines()
+            if f.close():
+                raise
+        except:
+                die("Could not find the \"p4\" command")
+
+    def p4(self, cmd, *args):
+        global logfile
+        cmd = "%s %s" % (cmd, ' '.join(args))
+        report(2, "P4:", cmd)
+        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
+        list = []
+        while 1:
+           try:
+                list.append(marshal.load(f))
+           except EOFError:
+                break
+        self.ret = f.close()
+        return list
+
+    def sync(self, id, force=False, trick=False, test=False):
+        if force:
+            ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
+        elif trick:
+            ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
+        elif test:
+            ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
+        else:
+            ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
+        if ret['code'] == "error":
+             data = ret['data'].upper()
+             if data.find('VIEW') > 0:
+                 die("Perforce reports %s is not in client view"% self.repopath)
+             elif data.find('UP-TO-DATE') < 0:
+                 die("Could not sync files from perforce", self.repopath)
+
+    def changes(self, since=0):
+        try:
+            list = []
+            for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
+                list.append(rec['change'])
+            list.reverse()
+            return list
+        except:
+            return []
+
+    def authors(self, filename):
+        f=open(filename)
+        for l in f.readlines():
+            self.userlist[l[:l.find('=')].rstrip()] = \
+                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
+        f.close()
+        for f,e in self.userlist.items():
+                report(2, f, ":", e[0], "  <", e[1], ">")
+
+    def _get_user(self, id):
+        if not self.userlist.has_key(id):
+            try:
+                user = self.p4("users", id)[0]
+                self.userlist[id] = (user['FullName'], user['Email'])
+            except:
+                self.userlist[id] = (id, "")
+        return self.userlist[id]
+
+    def _format_date(self, ticks):
+        symbol='+'
+        name = time.tzname[0]
+        offset = time.timezone
+        if ticks[8]:
+            name = time.tzname[1]
+            offset = time.altzone
+        if offset < 0:
+            offset *= -1
+            symbol = '-'
+        localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
+        tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
+        return "%s %s" % (tickso, localo)
+
+    def where(self):
+        try:
+            return self.p4("where %s" % self.repopath)[-1]['path']
+        except:
+            return ""
+
+    def describe(self, num):
+        desc = self.p4("describe -s", num)[0]
+        self.msg = desc['desc']
+        self.author, self.email = self._get_user(desc['user'])
+        self.date = self._format_date(time.localtime(long(desc['time'])))
+        return self
+
+class git_command:
+    def __init__(self):
+        try:
+            self.version = self.git("--version")[0][12:].rstrip()
+        except:
+            die("Could not find the \"git\" command")
+        try:
+            self.gitdir = self.get_single("rev-parse --git-dir")
+            report(2, "gdir:", self.gitdir)
+        except:
+            die("Not a git repository... did you forget to \"git init-db\" ?")
+        try:
+            self.cdup = self.get_single("rev-parse --show-cdup")
+            if self.cdup != "":
+                os.chdir(self.cdup)
+            self.topdir = os.getcwd()
+            report(2, "topdir:", self.topdir)
+        except:
+            die("Could not find top git directory")
+
+    def git(self, cmd):
+        global logfile
+        report(2, "GIT:", cmd)
+        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
+        r=f.readlines()
+        self.ret = f.close()
+        return r
+
+    def get_single(self, cmd):
+        return self.git(cmd)[0].rstrip()
+
+    def current_branch(self):
+        try:
+            testit = self.git("rev-parse --verify HEAD")[0]
+            return self.git("symbolic-ref HEAD")[0][11:].rstrip()
+        except:
+            return None
+
+    def get_config(self, variable):
+        try:
+            return self.git("repo-config --get %s" % variable)[0].rstrip()
+        except:
+            return None
+
+    def set_config(self, variable, value):
+        try:
+            self.git("repo-config %s %s"%(variable, value) )
+        except:
+            die("Could not set %s to " % variable, value)
+
+    def make_tag(self, name, head):
+        self.git("tag -f %s %s"%(name,head))
+
+    def top_change(self, branch):
+        try:
+            a=self.get_single("name-rev --tags refs/heads/%s" % branch)
+            loc = a.find(' tags/') + 6
+            if a[loc:loc+3] != "p4/":
+                raise
+            return int(a[loc+3:][:-2])
+        except:
+            return 0
+
+    def update_index(self):
+        self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
+
+    def checkout(self, branch):
+        self.git("checkout %s" % branch)
+
+    def repoint_head(self, branch):
+        self.git("symbolic-ref HEAD refs/heads/%s" % branch)
+
+    def remove_files(self):
+        self.git("ls-files | xargs rm")
+
+    def clean_directories(self):
+        self.git("clean -d")
+
+    def fresh_branch(self, branch):
+        report(1, "Creating new branch", branch)
+        self.git("ls-files | xargs rm")
+        os.remove(".git/index")
+        self.repoint_head(branch)
+        self.git("clean -d")
+
+    def basedir(self):
+        return self.topdir
+
+    def commit(self, author, email, date, msg, id):
+        self.update_index()
+        fd=open(".msg", "w")
+        fd.writelines(msg)
+        fd.close()
+        try:
+                current = self.get_single("rev-parse --verify HEAD")
+                head = "-p HEAD"
+        except:
+                current = ""
+                head = ""
+        tree = self.get_single("write-tree")
+        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
+            os.environ['GIT_AUTHOR_%s'%r] = l
+            os.environ['GIT_COMMITTER_%s'%r] = l
+        commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
+        os.remove(".msg")
+        self.make_tag("p4/%s"%id, commit)
+        self.git("update-ref HEAD %s %s" % (commit, current) )
+
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
+                    ["authors=","help","stitch=","timezone=","log=","ignore"])
+except getopt.GetoptError:
+    usage()
+
+for o, a in opts:
+    if o == "-q":
+        verbosity = 0
+    if o == "-v":
+        verbosity += 1
+    if o in ("--log"):
+        logfile = a
+    if o in ("-h", "--help"):
+        usage()
+    if o in ("--ignore"):
+        ignore_warnings = True
+
+git = git_command()
+branch=git.current_branch()
+
+for o, a in opts:
+    if o in ("-t", "--timezone"):
+        git.set_config("perforce.timezone", a)
+    if o in ("--stitch"):
+        git.set_config("perforce.%s.path" % branch, a)
+        stitch = 1
+
+if len(args) == 2:
+    branch = args[1]
+    git.checkout(branch)
+    if branch == git.current_branch():
+        die("Branch %s already exists!" % branch)
+    report(1, "Setting perforce to ", args[0])
+    git.set_config("perforce.%s.path" % branch, args[0])
+elif len(args) != 0:
+    die("You must specify the perforce //depot/path and git branch")
+
+p4path = git.get_config("perforce.%s.path" % branch)
+if p4path == None:
+    die("Do not know Perforce //depot/path for git branch", branch)
+
+p4 = p4_command(p4path)
+
+for o, a in opts:
+    if o in ("-a", "--authors"):
+        p4.authors(a)
+
+localdir = git.basedir()
+if p4.where()[:len(localdir)] != localdir:
+    report(1, "**WARNING** Appears p4 client is misconfigured")
+    report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
+    if ignore_warnings != True:
+        die("Reconfigure or use \"--ignore\" on command line")
+
+if stitch == 0:
+    top = git.top_change(branch)
+else:
+    top = 0
+changes = p4.changes(top)
+count = len(changes)
+if count == 0:
+    report(1, "Already up to date...")
+    sys.exit(0)
+
+ptz = git.get_config("perforce.timezone")
+if ptz:
+    report(1, "Setting timezone to", ptz)
+    os.environ['TZ'] = ptz
+    time.tzset()
+
+if stitch == 1:
+    git.remove_files()
+    git.clean_directories()
+    p4.sync(changes[0], force=True)
+elif top == 0 and branch != git.current_branch():
+    p4.sync(changes[0], test=True)
+    report(1, "Creating new initial commit");
+    git.fresh_branch(branch)
+    p4.sync(changes[0], force=True)
+else:
+    p4.sync(changes[0], trick=True)
+
+report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
+for id in changes:
+    report(1, "Importing changeset", id)
+    change = p4.describe(id)
+    p4.sync(id)
+    git.commit(change.author, change.email, change.date, change.msg, id)
+    if stitch == 1:
+        git.clean_directories()
+        stitch = 0
+
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
new file mode 100755 (executable)
index 0000000..187f088
--- /dev/null
@@ -0,0 +1,211 @@
+#!/bin/sh
+
+# git-ls-remote could be called from outside a git managed repository;
+# this would fail in that case and would issue an error message.
+GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
+
+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
+               ;;
+       *)
+               if test "$(git-repo-config --get "remote.$1.url")"
+               then
+                       echo config
+               elif test -f "$GIT_DIR/remotes/$1"
+               then
+                       echo remotes
+               elif test -f "$GIT_DIR/branches/$1"
+               then
+                       echo branches
+               else
+                       echo ''
+               fi ;;
+       esac
+}
+
+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"
+               ;;
+       config)
+               git-repo-config --get "remote.$1.url"
+               ;;
+       remotes)
+               sed -ne '/^URL: */{
+                       s///p
+                       q
+               }' "$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"
+               ;;
+       *)
+               die "internal error: get-remote-url $1" ;;
+       esac
+}
+
+get_remote_default_refs_for_push () {
+       data_source=$(get_data_source "$1")
+       case "$data_source" in
+       '' | config-partial | branches | branches-partial)
+               ;; # no default push mapping, just send matching refs.
+       config)
+               git-repo-config --get-all "remote.$1.push" ;;
+       remotes)
+               sed -ne '/^Push: */{
+                       s///p
+               }' "$GIT_DIR/remotes/$1" ;;
+       *)
+               die "internal error: get-remote-default-ref-for-push $1" ;;
+       esac
+}
+
+# Subroutine to canonicalize remote:local notation.
+canon_refs_list_for_fetch () {
+       # Leave only the first one alone; add prefix . to the rest
+       # to prevent the secondary branches to be merged by default.
+       dot_prefix=
+       for ref
+       do
+               force=
+               case "$ref" in
+               +*)
+                       ref=$(expr "z$ref" : 'z+\(.*\)')
+                       force=+
+                       ;;
+               esac
+               expr "z$ref" : 'z.*:' >/dev/null || ref="${ref}:"
+               remote=$(expr "z$ref" : 'z\([^:]*\):')
+               local=$(expr "z$ref" : 'z[^:]*:\(.*\)')
+               case "$remote" in
+               '') remote=HEAD ;;
+               refs/heads/* | refs/tags/* | refs/remotes/*) ;;
+               heads/* | tags/* | remotes/* ) remote="refs/$remote" ;;
+               *) remote="refs/heads/$remote" ;;
+               esac
+               case "$local" in
+               '') local= ;;
+               refs/heads/* | refs/tags/* | refs/remotes/*) ;;
+               heads/* | tags/* | remotes/* ) local="refs/$local" ;;
+               *) local="refs/heads/$local" ;;
+               esac
+
+               if local_ref_name=$(expr "z$local" : 'zrefs/\(.*\)')
+               then
+                  git-check-ref-format "$local_ref_name" ||
+                  die "* refusing to create funny ref '$local_ref_name' locally"
+               fi
+               echo "${dot_prefix}${force}${remote}:${local}"
+               dot_prefix=.
+       done
+}
+
+# Returns list of src: (no store), or src:dst (store)
+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 \
+                       $(git-repo-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
+               echo "refs/heads/${remote_branch}:refs/heads/$1"
+               ;;
+       remotes)
+               # This prefixes the second and later default refspecs
+               # with a '.', to signal git-fetch to mark them
+               # not-for-merge.
+               canon_refs_list_for_fetch $(sed -ne '/^Pull: */{
+                                               s///p
+                                       }' "$GIT_DIR/remotes/$1")
+               ;;
+       *)
+               die "internal error: get-remote-default-ref-for-push $1" ;;
+       esac
+}
+
+get_remote_refs_for_push () {
+       case "$#" in
+       0) die "internal error: get-remote-refs-for-push." ;;
+       1) get_remote_default_refs_for_push "$@" ;;
+       *) shift; echo "$@" ;;
+       esac
+}
+
+get_remote_refs_for_fetch () {
+       case "$#" in
+       0)
+           die "internal error: get-remote-refs-for-fetch." ;;
+       1)
+           get_remote_default_refs_for_fetch "$@" ;;
+       *)
+           shift
+           tag_just_seen=
+           for ref
+           do
+               if test "$tag_just_seen"
+               then
+                   echo "refs/tags/${ref}:refs/tags/${ref}"
+                   tag_just_seen=
+                   continue
+               else
+                   case "$ref" in
+                   tag)
+                       tag_just_seen=yes
+                       continue
+                       ;;
+                   esac
+               fi
+               canon_refs_list_for_fetch "$ref"
+           done
+           ;;
+       esac
+}
+
+resolve_alternates () {
+       # original URL (xxx.git)
+       top_=`expr "z$1" : 'z\([^:]*:/*[^/]*\)/'`
+       while read path
+       do
+               case "$path" in
+               \#* | '')
+                       continue ;;
+               /*)
+                       echo "$top_$path/" ;;
+               ../*)
+                       # relative -- ugly but seems to work.
+                       echo "$1/objects/$path/" ;;
+               *)
+                       # exit code may not be caught by the reader.
+                       echo "bad alternate: $path"
+                       exit 1 ;;
+               esac
+       done
+}
diff --git a/git-prune.sh b/git-prune.sh
new file mode 100755 (executable)
index 0000000..c5a5d29
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+USAGE='[-n] [--] [<head>...]'
+. git-sh-setup
+
+dryrun=
+echo=
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -n) dryrun=-n echo=echo ;;
+    --) break ;;
+    -*) usage ;;
+    *)  break ;;
+    esac
+    shift;
+done
+
+sync
+case "$#" in
+0) git-fsck-objects --full --cache --unreachable ;;
+*) git-fsck-objects --full --cache --unreachable $(git-rev-parse --all) "$@" ;;
+esac |
+
+sed -ne '/unreachable /{
+    s/unreachable [^ ][^ ]* //
+    s|\(..\)|\1/|p
+}' | {
+       cd "$GIT_OBJECT_DIRECTORY" || exit
+       xargs $echo rm -f
+       rmdir 2>/dev/null [0-9a-f][0-9a-f]
+}
+
+git-prune-packed $dryrun
+
+if redundant=$(git-pack-redundant --all 2>/dev/null) && test "" != "$redundant"
+then
+       if test "" = "$dryrun"
+       then
+               echo "$redundant" | xargs rm -f
+       else
+               echo rm -f "$redundant"
+       fi
+fi
diff --git a/git-pull.sh b/git-pull.sh
new file mode 100755 (executable)
index 0000000..4611ae6
--- /dev/null
@@ -0,0 +1,103 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Fetch one or more remote refs and merge it/them into the current HEAD.
+
+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.'
+. git-sh-setup
+
+strategy_args= no_summary= no_commit=
+while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac
+do
+       case "$1" in
+       -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
+               --no-summa|--no-summar|--no-summary)
+               no_summary=-n ;;
+       --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+               no_commit=--no-commit ;;
+       -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+               --strateg=*|--strategy=*|\
+       -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+               case "$#,$1" in
+               *,*=*)
+                       strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+               1,*)
+                       usage ;;
+               *)
+                       strategy="$2"
+                       shift ;;
+               esac
+               strategy_args="${strategy_args}-s $strategy "
+               ;;
+       -h|--h|--he|--hel|--help)
+               usage
+               ;;
+       -*)
+               # Pass thru anything that is meant for fetch.
+               break
+               ;;
+       esac
+       shift
+done
+
+orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?"
+git-fetch --update-head-ok "$@" || exit 1
+
+curr_head=$(git-rev-parse --verify HEAD)
+if test "$curr_head" != "$orig_head"
+then
+       # The fetch involved updating the current branch.
+
+       # The working tree and the index file is still based on the
+       # $orig_head commit, but we are merging into $curr_head.
+       # First update the working tree to match $curr_head.
+
+       echo >&2 "Warning: fetch updated the current branch head."
+       echo >&2 "Warning: fast forwarding your working tree from"
+       echo >&2 "Warning: $orig_head commit."
+       git-update-index --refresh 2>/dev/null
+       git-read-tree -u -m "$orig_head" "$curr_head" ||
+               die 'Cannot fast-forward your working tree.
+After making sure that you saved anything precious from
+$ git diff '$orig_head'
+output, run
+$ git reset --hard
+to recover.'
+
+fi
+
+merge_head=$(sed -e '/ not-for-merge   /d' \
+       -e 's/  .*//' "$GIT_DIR"/FETCH_HEAD | \
+       tr '\012' ' ')
+
+case "$merge_head" in
+'')
+       echo >&2 "No changes."
+       exit 0
+       ;;
+?*' '?*)
+       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
+
+case "$strategy_args" in
+'')
+       strategy_args=$strategy_default_args
+       ;;
+esac
+
+merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD")
+git-merge $no_summary $no_commit $strategy_args "$merge_name" HEAD $merge_head
diff --git a/git-push.sh b/git-push.sh
new file mode 100755 (executable)
index 0000000..f10cadb
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+USAGE='[--all] [--tags] [--force] <repository> [<refspec>...]'
+. git-sh-setup
+
+# Parse out parameters and then stop at remote, so that we can
+# translate it using .git/branches information
+has_all=
+has_force=
+has_exec=
+has_thin=--thin
+remote=
+do_tags=
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       --all)
+               has_all=--all ;;
+       --tags)
+               do_tags=yes ;;
+       --force)
+               has_force=--force ;;
+       --exec=*)
+               has_exec="$1" ;;
+       --thin)
+               ;; # noop
+       --no-thin)
+               has_thin= ;;
+       -*)
+                usage ;;
+        *)
+               set x "$@"
+               shift
+               break ;;
+       esac
+       shift
+done
+case "$#" in
+0)
+       echo "Where would you want to push today?"
+        usage ;;
+esac
+
+. git-parse-remote
+remote=$(get_remote_url "$@")
+
+case "$has_all" in
+--all)
+       set x ;;
+'')
+       case "$do_tags,$#" in
+       yes,1)
+               set x $(cd "$GIT_DIR/refs" && find tags -type f -print) ;;
+       yes,*)
+               set x $(cd "$GIT_DIR/refs" && find tags -type f -print) \
+                   $(get_remote_refs_for_push "$@") ;;
+       ,*)
+               set x $(get_remote_refs_for_push "$@") ;;
+       esac
+esac
+
+shift ;# away the initial 'x'
+
+# $# is now 0 if there was no explicit refspec on the command line
+# and there was no defalt refspec to push from remotes/ file.
+# we will let git-send-pack to do its "matching refs" thing.
+
+case "$remote" in
+git://*)
+       die "Cannot use READ-ONLY transport to push to $remote" ;;
+rsync://*)
+        die "Pushing with rsync transport is deprecated" ;;
+esac
+
+set x "$remote" "$@"; shift
+test "$has_all" && set x "$has_all" "$@" && shift
+test "$has_force" && set x "$has_force" "$@" && shift
+test "$has_exec" && set x "$has_exec" "$@" && shift
+test "$has_thin" && set x "$has_thin" "$@" && shift
+
+case "$remote" in
+http://* | https://*)
+       exec git-http-push "$@";;
+*)
+       exec git-send-pack "$@";;
+esac
diff --git a/git-quiltimport.sh b/git-quiltimport.sh
new file mode 100755 (executable)
index 0000000..12d9d0c
--- /dev/null
@@ -0,0 +1,118 @@
+#!/bin/sh
+USAGE='--dry-run --author <author> --patches </path/to/quilt/patch/directory>'
+SUBDIRECTORY_ON=Yes
+. git-sh-setup
+
+dry_run=""
+quilt_author=""
+while case "$#" in 0) break;; esac
+do
+       case "$1" in
+       --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+               quilt_author=$(expr "$1" : '-[^=]*\(.*\)')
+               shift
+               ;;
+
+       --au|--aut|--auth|--autho|--author)
+               case "$#" in 1) usage ;; esac
+               shift
+               quilt_author="$1"
+               shift
+               ;;
+
+       --dry-run)
+               shift
+               dry_run=1
+               ;;
+
+       --pa=*|--pat=*|--patc=*|--patch=*|--patche=*|--patches=*)
+               QUILT_PATCHES=$(expr "$1" : '-[^=]*\(.*\)')
+               shift
+               ;;
+
+       --pa|--pat|--patc|--patch|--patche|--patches)
+               case "$#" in 1) usage ;; esac
+               shift
+               QUILT_PATCHES="$1"
+               shift
+               ;;
+
+       *)
+               break
+               ;;
+       esac
+done
+
+# Quilt Author
+if [ -n "$quilt_author" ] ; then
+       quilt_author_name=$(expr "z$quilt_author" : 'z\(.*[^ ]\) *<.*') &&
+       quilt_author_email=$(expr "z$quilt_author" : '.*<\([^>]*\)') &&
+       test '' != "$quilt_author_name" &&
+       test '' != "$quilt_author_email" ||
+       die "malformatted --author parameter"
+fi
+
+# Quilt patch directory
+: ${QUILT_PATCHES:=patches}
+if ! [ -d "$QUILT_PATCHES" ] ; then
+       echo "The \"$QUILT_PATCHES\" directory does not exist."
+       exit 1
+fi
+
+# Temporay directories
+tmp_dir=.dotest
+tmp_msg="$tmp_dir/msg"
+tmp_patch="$tmp_dir/patch"
+tmp_info="$tmp_dir/info"
+
+
+# Find the intial commit
+commit=$(git-rev-parse HEAD)
+
+mkdir $tmp_dir || exit 2
+for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
+       echo $patch_name
+       (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3
+
+       # Parse the author information
+       export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info")
+       export GIT_AUTHOR_EMAIL=$(sed -ne 's/Email: //p' "$tmp_info")
+       while test -z "$GIT_AUTHOR_EMAIL" && test -z "$GIT_AUTHOR_NAME" ; do
+               if [ -n "$quilt_author" ] ; then
+                       GIT_AUTHOR_NAME="$quilt_author_name";
+                       GIT_AUTHOR_EMAIL="$quilt_author_email";
+               elif [ -n "$dry_run" ]; then
+                       echo "No author found in $patch_name" >&2;
+                       GIT_AUTHOR_NAME="dry-run-not-found";
+                       GIT_AUTHOR_EMAIL="dry-run-not-found";
+               else
+                       echo "No author found in $patch_name" >&2;
+                       echo "---"
+                       cat $tmp_msg
+                       echo -n "Author: ";
+                       read patch_author
+
+                       echo "$patch_author"
+
+                       patch_author_name=$(expr "z$patch_author" : 'z\(.*[^ ]\) *<.*') &&
+                       patch_author_email=$(expr "z$patch_author" : '.*<\([^>]*\)') &&
+                       test '' != "$patch_author_name" &&
+                       test '' != "$patch_author_email" &&
+                       GIT_AUTHOR_NAME="$patch_author_name" &&
+                       GIT_AUTHOR_EMAIL="$patch_author_email"
+               fi
+       done
+       export GIT_AUTHOR_DATE=$(sed -ne 's/Date: //p' "$tmp_info")
+       export SUBJECT=$(sed -ne 's/Subject: //p' "$tmp_info")
+       if [ -z "$SUBJECT" ] ; then
+               SUBJECT=$(echo $patch_name | sed -e 's/.patch$//')
+       fi
+
+       if [ -z "$dry_run" ] ; then
+               git-apply --index -C1 "$tmp_patch" &&
+               tree=$(git-write-tree) &&
+               commit=$((echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
+               git-update-ref HEAD $commit || exit 4
+       fi
+done
+rm -rf $tmp_dir || exit 5
diff --git a/git-rebase.sh b/git-rebase.sh
new file mode 100755 (executable)
index 0000000..e6b57b8
--- /dev/null
@@ -0,0 +1,157 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+USAGE='[--onto <newbase>] <upstream> [<branch>]'
+LONG_USAGE='git-rebase replaces <branch> with a new branch of the
+same name.  When the --onto option is provided the new branch starts
+out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
+It then attempts to create a new commit for each commit from the original
+<branch> that does not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run git rebase --continue.  Another option is to bypass the commit
+that caused the merge failure with git rebase --skip.  To restore the
+original <branch> and remove the .dotest working files, use the command
+git rebase --abort instead.
+
+Note that if <branch> is not specified on the command line, the
+currently checked out branch is used.  You must be in the top
+directory of your project to start (or continue) a rebase.
+
+Example:       git-rebase master~1 topic
+
+        A---B---C topic                   A'\''--B'\''--C'\'' topic
+       /                   -->           /
+  D---E---F---G master          D---E---F---G master
+'
+. git-sh-setup
+
+RESOLVEMSG="
+When you have resolved this problem run \"git rebase --continue\".
+If you would prefer to skip this patch, instead run \"git rebase --skip\".
+To restore the original branch and stop rebasing run \"git rebase --abort\".
+"
+unset newbase
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       --continue)
+               diff=$(git-diff-files)
+               case "$diff" in
+               ?*)     echo "You must edit all merge conflicts and then"
+                       echo "mark them as resolved using git update-index"
+                       exit 1
+                       ;;
+               esac
+               git am --resolved --3way --resolvemsg="$RESOLVEMSG"
+               exit
+               ;;
+       --skip)
+               git am -3 --skip --resolvemsg="$RESOLVEMSG"
+               exit
+               ;;
+       --abort)
+               [ -d .dotest ] || die "No rebase in progress?"
+               git reset --hard ORIG_HEAD
+               rm -r .dotest
+               exit
+               ;;
+       --onto)
+               test 2 -le "$#" || usage
+               newbase="$2"
+               shift
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+done
+
+# Make sure we do not have .dotest
+if mkdir .dotest
+then
+       rmdir .dotest
+else
+       echo >&2 '
+It seems that I cannot create a .dotest directory, and I wonder if you
+are in the middle of patch application or another rebase.  If that is not
+the case, please rm -fr .dotest and run me again.  I am stopping in case
+you still have something valuable there.'
+       exit 1
+fi
+
+# The tree must be really really clean.
+git-update-index --refresh || exit
+diff=$(git-diff-index --cached --name-status -r HEAD)
+case "$diff" in
+?*)    echo "$diff"
+       exit 1
+       ;;
+esac
+
+# The upstream head must be given.  Make sure it is valid.
+upstream_name="$1"
+upstream=`git rev-parse --verify "${upstream_name}^0"` ||
+    die "invalid upstream $upstream_name"
+
+# If a hook exists, give it a chance to interrupt
+if test -x "$GIT_DIR/hooks/pre-rebase"
+then
+       "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || {
+               echo >&2 "The pre-rebase hook refused to rebase."
+               exit 1
+       }
+fi
+
+# If the branch to rebase is given, first switch to it.
+case "$#" in
+2)
+       branch_name="$2"
+       git-checkout "$2" || usage
+       ;;
+*)
+       branch_name=`git symbolic-ref HEAD` || die "No current branch"
+       branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+       ;;
+esac
+branch=$(git-rev-parse --verify "${branch_name}^0") || exit
+
+# Make sure the branch to rebase onto is valid.
+onto_name=${newbase-"$upstream_name"}
+onto=$(git-rev-parse --verify "${onto_name}^0") || exit
+
+# Now we are rebasing commits $upstream..$branch on top of $onto
+
+# Check if we are already based on $onto, but this should be
+# done only when upstream and onto are the same.
+if test "$upstream" = "$onto"
+then
+       mb=$(git-merge-base "$onto" "$branch")
+       if test "$mb" = "$onto"
+       then
+               echo >&2 "Current branch $branch_name is up to date."
+               exit 0
+       fi
+fi
+
+# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
+git-reset --hard "$onto"
+
+# If the $onto is a proper descendant of the tip of the branch, then
+# we just fast forwarded.
+if test "$mb" = "$onto"
+then
+       echo >&2 "Fast-forwarded $branch to $newbase."
+       exit 0
+fi
+
+git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
+git am --binary -3 -k --resolvemsg="$RESOLVEMSG"
+
diff --git a/git-relink.perl b/git-relink.perl
new file mode 100755 (executable)
index 0000000..f6b4f6a
--- /dev/null
@@ -0,0 +1,173 @@
+#!/usr/bin/env perl
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+# Distribution permitted under the GPL v2, as distributed
+# by the Free Software Foundation.
+# Later versions of the GPL at the discretion of Linus Torvalds
+#
+# Scan two git object-trees, and hardlink any common objects between them.
+
+use 5.006;
+use strict;
+use warnings;
+use Getopt::Long;
+
+sub get_canonical_form($);
+sub do_scan_directory($$$);
+sub compare_two_files($$);
+sub usage();
+sub link_two_files($$);
+
+# stats
+my $total_linked = 0;
+my $total_already = 0;
+my ($linked,$already);
+
+my $fail_on_different_sizes = 0;
+my $help = 0;
+GetOptions("safe" => \$fail_on_different_sizes,
+          "help" => \$help);
+
+usage() if $help;
+
+my (@dirs) = @ARGV;
+
+usage() if (!defined $dirs[0] || !defined $dirs[1]);
+
+$_ = get_canonical_form($_) foreach (@dirs);
+
+my $master_dir = pop @dirs;
+
+opendir(D,$master_dir . "objects/")
+       or die "Failed to open $master_dir/objects/ : $!";
+
+my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
+
+foreach my $repo (@dirs) {
+       $linked = 0;
+       $already = 0;
+       printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",
+               $master_dir,$repo);
+
+       foreach my $hashdir (@hashdirs) {
+               do_scan_directory($master_dir, $hashdir, $repo);
+       }
+
+       printf("Linked %d files, %d were already linked.\n",$linked, $already);
+
+       $total_linked += $linked;
+       $total_already += $already;
+}
+
+printf("Totals: Linked %d files, %d were already linked.\n",
+       $total_linked, $total_already);
+
+
+sub do_scan_directory($$$) {
+       my ($srcdir, $subdir, $dstdir) = @_;
+
+       my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
+       my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
+
+       opendir(S,$sfulldir)
+               or die "Failed to opendir $sfulldir: $!";
+
+       foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
+               my $sfilename = $sfulldir . $file;
+               my $dfilename = $dfulldir . $file;
+
+               compare_two_files($sfilename,$dfilename);
+
+       }
+       closedir(S);
+}
+
+sub compare_two_files($$) {
+       my ($sfilename, $dfilename) = @_;
+
+       # Perl's stat returns relevant information as follows:
+       # 0 = dev number
+       # 1 = inode number
+       # 7 = size
+       my @sstatinfo = stat($sfilename);
+       my @dstatinfo = stat($dfilename);
+
+       if (@sstatinfo == 0 && @dstatinfo == 0) {
+               die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
+
+       } elsif (@dstatinfo == 0) {
+               return;
+       }
+
+       if ( ($sstatinfo[0] == $dstatinfo[0]) &&
+            ($sstatinfo[1] != $dstatinfo[1])) {
+               if ($sstatinfo[7] == $dstatinfo[7]) {
+                       link_two_files($sfilename, $dfilename);
+
+               } else {
+                       my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
+                               $sfilename, $dfilename);
+                       if ($fail_on_different_sizes) {
+                               die $err;
+                       } else {
+                               warn $err;
+                       }
+               }
+
+       } elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
+            ($sstatinfo[1] == $dstatinfo[1])) {
+               $already++;
+       }
+}
+
+sub get_canonical_form($) {
+       my $dir = shift;
+       my $original = $dir;
+
+       die "$dir is not a directory." unless -d $dir;
+
+       $dir .= "/" unless $dir =~ m#/$#;
+       $dir .= ".git/" unless $dir =~ m#\.git/$#;
+
+       die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
+
+       return $dir;
+}
+
+sub link_two_files($$) {
+       my ($sfilename, $dfilename) = @_;
+       my $tmpdname = sprintf("%s.old",$dfilename);
+       rename($dfilename,$tmpdname)
+               or die sprintf("Failure renaming %s to %s: %s",
+                       $dfilename, $tmpdname, $!);
+
+       if (! link($sfilename,$dfilename)) {
+               my $failtxt = "";
+               unless (rename($tmpdname,$dfilename)) {
+                       $failtxt = sprintf(
+                               "Git Repository containing %s is probably corrupted, " .
+                               "please copy '%s' to '%s' to fix.\n",
+                               $tmpdname, $dfilename);
+               }
+
+               die sprintf("Failed to link %s to %s: %s\n%s" .
+                       $sfilename, $dfilename,
+                       $!, $dfilename, $failtxt);
+       }
+
+       unlink($tmpdname)
+               or die sprintf("Unlink of %s failed: %s\n",
+                       $dfilename, $!);
+
+       $linked++;
+}
+
+
+sub usage() {
+       print("Usage: $0 [--safe] <dir> [<dir> ...] <master_dir> \n");
+       print("All directories should contain a .git/objects/ subdirectory.\n");
+       print("Options\n");
+       print("\t--safe\t" .
+               "Stops if two objects with the same hash exist but " .
+               "have different sizes.  Default is to warn and continue.\n");
+       exit(1);
+}
diff --git a/git-repack.sh b/git-repack.sh
new file mode 100755 (executable)
index 0000000..4fb3f26
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
+. git-sh-setup
+
+no_update_info= all_into_one= remove_redundant=
+local= quiet= no_reuse_delta= extra=
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -n)     no_update_info=t ;;
+       -a)     all_into_one=t ;;
+       -d)     remove_redundant=t ;;
+       -q)     quiet=-q ;;
+       -f)     no_reuse_delta=--no-reuse-delta ;;
+       -l)     local=--local ;;
+       --window=*) extra="$extra $1" ;;
+       --depth=*) extra="$extra $1" ;;
+       *)      usage ;;
+       esac
+       shift
+done
+
+rm -f .tmp-pack-*
+PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
+
+# There will be more repacking strategies to come...
+case ",$all_into_one," in
+,,)
+       rev_list='--unpacked'
+       pack_objects='--incremental'
+       ;;
+,t,)
+       rev_list=
+       pack_objects=
+
+       # Redundancy check in all-into-one case is trivial.
+       existing=`cd "$PACKDIR" && \
+           find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+       ;;
+esac
+pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
+name=$(git-rev-list --objects --all $rev_list 2>&1 |
+       git-pack-objects --non-empty $pack_objects .tmp-pack) ||
+       exit 1
+if [ -z "$name" ]; then
+       echo Nothing new to pack.
+else
+       echo "Pack pack-$name created."
+
+       mkdir -p "$PACKDIR" || exit
+
+       mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+       mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
+       exit
+fi
+
+if test "$remove_redundant" = t
+then
+       # We know $existing are all redundant only when
+       # all-into-one is used.
+       if test "$all_into_one" != '' && test "$existing" != ''
+       then
+               sync
+               ( cd "$PACKDIR" &&
+                 for e in $existing
+                 do
+                       case "$e" in
+                       ./pack-$name.pack | ./pack-$name.idx) ;;
+                       *)      rm -f $e ;;
+                       esac
+                 done
+               )
+       fi
+       git-prune-packed
+fi
+
+case "$no_update_info" in
+t) : ;;
+*) git-update-server-info ;;
+esac
diff --git a/git-request-pull.sh b/git-request-pull.sh
new file mode 100755 (executable)
index 0000000..4319e35
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh -e
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+USAGE='<commit> <url> [<head>]'
+LONG_USAGE='Summarizes the changes since <commit> to the standard output,
+and includes <url> in the message generated.'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+revision=$1
+url=$2
+head=${3-HEAD}
+
+[ "$revision" ] || usage
+[ "$url" ] || usage
+
+baserev=`git-rev-parse --verify "$revision"^0` &&
+headrev=`git-rev-parse --verify "$head"^0` || exit
+
+echo "The following changes since commit $baserev:"
+git log --max-count=1 --pretty=short "$baserev" |
+git-shortlog | sed -e 's/^\(.\)/  \1/'
+
+echo "are found in the git repository at:" 
+echo
+echo "  $url"
+echo
+
+git log  $baserev..$headrev | git-shortlog ;
+git diff --stat --summary $baserev..$headrev
diff --git a/git-rerere.perl b/git-rerere.perl
new file mode 100755 (executable)
index 0000000..d3664ff
--- /dev/null
@@ -0,0 +1,227 @@
+#!/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();
diff --git a/git-reset.sh b/git-reset.sh
new file mode 100755 (executable)
index 0000000..296f3b7
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+USAGE='[--mixed | --soft | --hard]  [<commit-ish>]'
+. git-sh-setup
+
+tmp=${GIT_DIR}/reset.$$
+trap 'rm -f $tmp-*' 0 1 2 3 15
+
+update=
+reset_type=--mixed
+case "$1" in
+--mixed | --soft | --hard)
+       reset_type="$1"
+       shift
+       ;;
+-*)
+        usage ;;
+esac
+
+rev=$(git-rev-parse --verify --default HEAD "$@") || exit
+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.
+if test "$reset_type" = "--hard"
+then
+       update=-u
+fi
+
+# Soft reset does not touch the index file nor the working tree
+# at all, but requires them in a good order.  Other resets reset
+# the index file to the tree object we are switching to.
+if test "$reset_type" = "--soft"
+then
+       if test -f "$GIT_DIR/MERGE_HEAD" ||
+          test "" != "$(git-ls-files --unmerged)"
+       then
+               die "Cannot do a soft reset in the middle of a merge."
+       fi
+else
+       git-read-tree --reset $update "$rev" || exit
+fi
+
+# Any resets update HEAD to the head being switched to.
+if orig=$(git-rev-parse --verify HEAD 2>/dev/null)
+then
+       echo "$orig" >"$GIT_DIR/ORIG_HEAD"
+else
+       rm -f "$GIT_DIR/ORIG_HEAD"
+fi
+git-update-ref -m "reset $reset_type $@" HEAD "$rev"
+
+case "$reset_type" in
+--hard )
+       ;; # Nothing else to do
+--soft )
+       ;; # Nothing else to do
+--mixed )
+       # Report what has not been updated.
+       git-update-index --refresh
+       ;;
+esac
+
+rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR"
diff --git a/git-resolve.sh b/git-resolve.sh
new file mode 100755 (executable)
index 0000000..1c7aaef
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees.
+#
+
+USAGE='<head> <remote> <merge-message>'
+. git-sh-setup
+
+dropheads() {
+       rm -f -- "$GIT_DIR/MERGE_HEAD" \
+               "$GIT_DIR/LAST_MERGE" || exit 1
+}
+
+head=$(git-rev-parse --verify "$1"^0) &&
+merge=$(git-rev-parse --verify "$2"^0) &&
+merge_msg="$3" || usage
+
+#
+# The remote name is just used for the message,
+# but we do want it.
+#
+if [ -z "$head" -o -z "$merge" -o -z "$merge_msg" ]; then
+       usage
+fi
+
+dropheads
+echo $head > "$GIT_DIR"/ORIG_HEAD
+echo $merge > "$GIT_DIR"/LAST_MERGE
+
+common=$(git-merge-base $head $merge)
+if [ -z "$common" ]; then
+       die "Unable to find common commit between" $merge $head
+fi
+
+case "$common" in
+"$merge")
+       echo "Already up-to-date. Yeeah!"
+       dropheads
+       exit 0
+       ;;
+"$head")
+       echo "Updating from $head to $merge"
+       git-read-tree -u -m $head $merge || exit 1
+       git-update-ref HEAD "$merge" "$head"
+       git-diff-tree -p $head $merge | git-apply --stat
+       dropheads
+       exit 0
+       ;;
+esac
+
+# We are going to make a new commit.
+git var GIT_COMMITTER_IDENT >/dev/null || exit
+
+# Find an optimum merge base if there are more than one candidates.
+LF='
+'
+common=$(git-merge-base -a $head $merge)
+case "$common" in
+?*"$LF"?*)
+       echo "Trying to find the optimum merge base."
+       G=.tmp-index$$
+       best=
+       best_cnt=-1
+       for c in $common
+       do
+               rm -f $G
+               GIT_INDEX_FILE=$G git-read-tree -m $c $head $merge \
+                       2>/dev/null || continue
+               # Count the paths that are unmerged.
+               cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+               if test $best_cnt -le 0 -o $cnt -le $best_cnt
+               then
+                       best=$c
+                       best_cnt=$cnt
+                       if test "$best_cnt" -eq 0
+                       then
+                               # Cannot do any better than all trivial merge.
+                               break
+                       fi
+               fi
+       done
+       rm -f $G
+       common="$best"
+esac
+
+echo "Trying to merge $merge into $head using $common."
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m $common $head $merge || exit 1
+result_tree=$(git-write-tree  2> /dev/null)
+if [ $? -ne 0 ]; then
+       echo "Simple merge failed, trying Automatic merge"
+       git-merge-index -o git-merge-one-file -a
+       if [ $? -ne 0 ]; then
+               echo $merge > "$GIT_DIR"/MERGE_HEAD
+               die "Automatic merge failed, fix up by hand"
+       fi
+       result_tree=$(git-write-tree) || exit 1
+fi
+result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
+echo "Committed merge $result_commit"
+git-update-ref HEAD "$result_commit" "$head"
+git-diff-tree -p $head $result_commit | git-apply --stat
+dropheads
diff --git a/git-revert.sh b/git-revert.sh
new file mode 100755 (executable)
index 0000000..de8b5f0
--- /dev/null
@@ -0,0 +1,170 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
+
+case "$0" in
+*-revert* )
+       test -t 0 && edit=-e
+       me=revert
+       USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
+*-cherry-pick* )
+       edit=
+       me=cherry-pick
+       USAGE='[--edit] [-n] [-r] <commit-ish>'  ;;
+* )
+       die "What are you talking about?" ;;
+esac
+. git-sh-setup
+
+no_commit= replay=
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+           --no-commi|--no-commit)
+               no_commit=t
+               ;;
+       -e|--e|--ed|--edi|--edit)
+               edit=-e
+               ;;
+       --n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
+               edit=
+               ;;
+       -r|--r|--re|--rep|--repl|--repla|--replay)
+               replay=t
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+done
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+       # We do not intend to commit immediately.  We just want to
+       # merge the differences in.
+       head=$(git-write-tree) ||
+               die "Your index file is unmerged."
+       ;;
+*)
+       head=$(git-rev-parse --verify HEAD) ||
+               die "You do not have a valid HEAD"
+       files=$(git-diff-index --cached --name-only $head) || exit
+       if [ "$files" ]; then
+               die "Dirty index: cannot $me (dirty: $files)"
+       fi
+       ;;
+esac
+
+rev=$(git-rev-parse --verify "$@") &&
+commit=$(git-rev-parse --verify "$rev^0") ||
+       die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+       die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+       die "Cannot run $me a multi-parent commit."
+
+# "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
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+       git-rev-list --pretty=oneline --max-count=1 $commit |
+       sed -e '
+               s/^[^ ]* /Revert "/
+               s/$/"/'
+       echo
+       echo "This reverts $commit commit."
+       test "$rev" = "$commit" ||
+       echo "(original 'git revert' arguments: $@)"
+       base=$commit next=$prev
+       ;;
+
+cherry-pick)
+       pick_author_script='
+       /^author /{
+               s/'\''/'\''\\'\'\''/g
+               h
+               s/^author \([^<]*\) <[^>]*> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+               g
+               s/^author [^<]* <\([^>]*\)> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+               g
+               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+               q
+       }'
+       set_author_env=`git-cat-file commit "$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
+
+       git-cat-file commit $commit | sed -e '1,/^$/d'
+       case "$replay" in
+       '')
+               echo "(cherry picked from $commit commit)"
+               test "$rev" = "$commit" ||
+               echo "(original 'git cherry-pick' arguments: $@)"
+               ;;
+       esac
+       base=$prev next=$commit
+       ;;
+
+esac >.msg
+
+# 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 &&
+result=$(git-write-tree 2>/dev/null) || {
+    echo >&2 "Simple $me fails; trying Automatic $me."
+    git-merge-index -o git-merge-one-file -a || {
+           echo >&2 "Automatic $me failed.  After resolving the conflicts,"
+           echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
+           echo >&2 "and commit with 'git commit -F .msg'"
+           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
+}
+echo >&2 "Finished one $me."
+
+# If we are cherry-pick, and if the merge did not result in
+# hand-editing, we will hit this commit and inherit the original
+# author date and name.
+# If we are revert, or if our cherry-pick results in a hand merge,
+# we had better say that the current user is responsible for that.
+
+case "$no_commit" in
+'')
+       git-commit -n -F .msg $edit
+       rm -f .msg
+       ;;
+esac
diff --git a/git-send-email.perl b/git-send-email.perl
new file mode 100755 (executable)
index 0000000..7b1cca7
--- /dev/null
@@ -0,0 +1,536 @@
+#!/usr/bin/perl -w
+#
+# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
+# Copyright 2005 Ryan Anderson <ryan@michonline.com>
+#
+# GPL v2 (See COPYING)
+#
+# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com>
+#
+# Sends a collection of emails to the given email addresses, disturbingly fast.
+#
+# Supports two formats:
+# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches)
+# 2. The original format support by Greg's script:
+#    first line of the message is who to CC,
+#    and second line is the subject of the message.
+#
+
+use strict;
+use warnings;
+use Term::ReadLine;
+use Getopt::Long;
+use Data::Dumper;
+
+# most mail servers generate the Date: header, but not all...
+$ENV{LC_ALL} = 'C';
+use POSIX qw/strftime/;
+
+my $have_email_valid = eval { require Email::Valid; 1 };
+my $smtp;
+
+sub unique_email_list(@);
+sub cleanup_compose_files();
+
+# Constants (essentially)
+my $compose_filename = ".msg.$$";
+
+# Variables we fill in automatically, or via prompting:
+my (@to,@cc,@initial_cc,@bcclist,
+       $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
+
+# Behavior modification variables
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my $smtp_server;
+
+# Example reply to:
+#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+
+my $term = new Term::ReadLine 'git-send-email';
+
+# Begin by accumulating all the variables (defined above), that we will end up
+# needing, first, from the command line:
+
+my $rc = GetOptions("from=s" => \$from,
+                    "in-reply-to=s" => \$initial_reply_to,
+                   "subject=s" => \$initial_subject,
+                   "to=s" => \@to,
+                   "cc=s" => \@initial_cc,
+                   "bcc=s" => \@bcclist,
+                   "chain-reply-to!" => \$chain_reply_to,
+                   "smtp-server=s" => \$smtp_server,
+                   "compose" => \$compose,
+                   "quiet" => \$quiet,
+                   "suppress-from" => \$suppress_from,
+                   "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+        );
+
+# Now, let's fill any that aren't set in with defaults:
+
+sub gitvar {
+    my ($var) = @_;
+    my $fh;
+    my $pid = open($fh, '-|');
+    die "$!" unless defined $pid;
+    if (!$pid) {
+       exec('git-var', $var) or die "$!";
+    }
+    my ($val) = <$fh>;
+    close $fh or die "$!";
+    chomp($val);
+    return $val;
+}
+
+sub gitvar_ident {
+    my ($name) = @_;
+    my $val = gitvar($name);
+    my @field = split(/\s+/, $val);
+    return join(' ', @field[0...(@field-3)]);
+}
+
+my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
+my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
+
+my %aliases;
+chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
+chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my %parse_alias = (
+       # multiline formats can be supported in the future
+       mutt => sub { my $fh = shift; while (<$fh>) {
+               if (/^alias\s+(\S+)\s+(.*)$/) {
+                       my ($alias, $addr) = ($1, $2);
+                       $addr =~ s/#.*$//; # mutt allows # comments
+                        # commas delimit multiple addresses
+                       $aliases{$alias} = [ split(/\s*,\s*/, $addr) ];
+               }}},
+       mailrc => sub { my $fh = shift; while (<$fh>) {
+               if (/^alias\s+(\S+)\s+(.*)$/) {
+                       # spaces delimit multiple addresses
+                       $aliases{$1} = [ split(/\s+/, $2) ];
+               }}},
+       pine => sub { my $fh = shift; while (<$fh>) {
+               if (/^(\S+)\s+(.*)$/) {
+                       $aliases{$1} = [ split(/\s*,\s*/, $2) ];
+               }}},
+       gnus => sub { my $fh = shift; while (<$fh>) {
+               if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
+                       $aliases{$1} = [ $2 ];
+               }}}
+);
+
+if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+       foreach my $file (@alias_files) {
+               open my $fh, '<', $file or die "opening $file: $!\n";
+               $parse_alias{$aliasfiletype}->($fh);
+               close $fh;
+       }
+}
+
+my $prompting = 0;
+if (!defined $from) {
+       $from = $author || $committer;
+       do {
+               $_ = $term->readline("Who should the emails appear to be from? ",
+                       $from);
+       } while (!defined $_);
+
+       $from = $_;
+       print "Emails will be sent from: ", $from, "\n";
+       $prompting++;
+}
+
+if (!@to) {
+       do {
+               $_ = $term->readline("Who should the emails be sent to? ",
+                               "");
+       } while (!defined $_);
+       my $to = $_;
+       push @to, split /,/, $to;
+       $prompting++;
+}
+
+sub expand_aliases {
+       my @cur = @_;
+       my @last;
+       do {
+               @last = @cur;
+               @cur = map { $aliases{$_} ? @{$aliases{$_}} : $_ } @last;
+       } while (join(',',@cur) ne join(',',@last));
+       return @cur;
+}
+
+@to = expand_aliases(@to);
+@initial_cc = expand_aliases(@initial_cc);
+@bcclist = expand_aliases(@bcclist);
+
+if (!defined $initial_subject && $compose) {
+       do {
+               $_ = $term->readline("What subject should the emails start with? ",
+                       $initial_subject);
+       } while (!defined $_);
+       $initial_subject = $_;
+       $prompting++;
+}
+
+if (!defined $initial_reply_to && $prompting) {
+       do {
+               $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
+                       $initial_reply_to);
+       } while (!defined $_);
+
+       $initial_reply_to = $_;
+       $initial_reply_to =~ s/(^\s+|\s+$)//g;
+}
+
+if (!$smtp_server) {
+       foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+               if (-x $_) {
+                       $smtp_server = $_;
+                       last;
+               }
+       }
+       $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
+}
+
+if ($compose) {
+       # Note that this does not need to be secure, but we will make a small
+       # effort to have it be unique
+       open(C,">",$compose_filename)
+               or die "Failed to open for writing $compose_filename: $!";
+       print C "From $from # This line is ignored.\n";
+       printf C "Subject: %s\n\n", $initial_subject;
+       printf C <<EOT;
+GIT: Please enter your email below.
+GIT: Lines beginning in "GIT: " will be removed.
+GIT: Consider including an overall diffstat or table of contents
+GIT: for the patch you are writing.
+
+EOT
+       close(C);
+
+       my $editor = $ENV{EDITOR};
+       $editor = 'vi' unless defined $editor;
+       system($editor, $compose_filename);
+
+       open(C2,">",$compose_filename . ".final")
+               or die "Failed to open $compose_filename.final : " . $!;
+
+       open(C,"<",$compose_filename)
+               or die "Failed to open $compose_filename : " . $!;
+
+       while(<C>) {
+               next if m/^GIT: /;
+               print C2 $_;
+       }
+       close(C);
+       close(C2);
+
+       do {
+               $_ = $term->readline("Send this email? (y|n) ");
+       } while (!defined $_);
+
+       if (uc substr($_,0,1) ne 'Y') {
+               cleanup_compose_files();
+               exit(0);
+       }
+
+       @files = ($compose_filename . ".final");
+}
+
+
+# Now that all the defaults are set, process the rest of the command line
+# arguments and collect up the files that need to be processed.
+for my $f (@ARGV) {
+       if (-d $f) {
+               opendir(DH,$f)
+                       or die "Failed to opendir $f: $!";
+
+               push @files, grep { -f $_ } map { +$f . "/" . $_ }
+                               sort readdir(DH);
+
+       } elsif (-f $f) {
+               push @files, $f;
+
+       } else {
+               print STDERR "Skipping $f - not found.\n";
+       }
+}
+
+if (@files) {
+       unless ($quiet) {
+               print $_,"\n" for (@files);
+       }
+} else {
+       print <<EOT;
+git-send-email [options] <file | directory> [... file | directory ]
+Options:
+   --from         Specify the "From:" line of the email to be sent.
+
+   --to           Specify the primary "To:" line of the email.
+
+   --cc           Specify an initial "Cc:" list for the entire series
+                  of emails.
+
+   --bcc          Specify a list of email addresses that should be Bcc:
+                 on all the emails.
+
+   --compose      Use \$EDITOR to edit an introductory message for the
+                  patch series.
+
+   --subject      Specify the initial "Subject:" line.
+                  Only necessary if --compose is also set.  If --compose
+                 is not set, this will be prompted for.
+
+   --in-reply-to  Specify the first "In-Reply-To:" header line.
+                  Only used if --compose is also set.  If --compose is not
+                 set, this will be prompted for.
+
+   --chain-reply-to If set, the replies will all be to the previous
+                  email sent, rather than to the first email sent.
+                  Defaults to on.
+
+   --no-signed-off-cc Suppress the automatic addition of email addresses
+                 that appear in a Signed-off-by: line, to the cc: list.
+                Note: Using this option is not recommended.
+
+   --smtp-server  If set, specifies the outgoing SMTP server to use.
+                  Defaults to localhost.
+
+  --suppress-from Supress sending emails to yourself if your address
+                  appears in a From: line.
+
+   --quiet     Make git-send-email less verbose.  One line per email should be
+               all that is output.
+
+Error: Please specify a file or a directory on the command line.
+EOT
+       exit(1);
+}
+
+# Variables we set as part of the loop over files
+our ($message_id, $cc, %mail, $subject, $reply_to, $references, $message);
+
+sub extract_valid_address {
+       my $address = shift;
+       my $local_part_regexp = '[^<>"\s@]+';
+       my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
+
+       # check for a local address:
+       return $address if ($address =~ /^($local_part_regexp)$/);
+
+       if ($have_email_valid) {
+               return scalar Email::Valid->address($address);
+       } else {
+               # less robust/correct than the monster regexp in Email::Valid,
+               # but still does a 99% job, and one less dependency
+               $address =~ /($local_part_regexp\@$domain_regexp)/;
+               return $1;
+       }
+}
+
+# Usually don't need to change anything below here.
+
+# we make a "fake" message id by taking the current number
+# of seconds since the beginning of Unix time and tacking on
+# a random number to the end, in case we are called quicker than
+# 1 second since the last time we were called.
+
+# We'll setup a template for the message id, using the "from" address:
+my $message_id_from = extract_valid_address($from);
+my $message_id_template = "<%s-git-send-email-$message_id_from>";
+
+sub make_message_id
+{
+       my $date = time;
+       my $pseudo_rand = int (rand(4200));
+       $message_id = sprintf $message_id_template, "$date$pseudo_rand";
+       #print "new message id = $message_id\n"; # Was useful for debugging
+}
+
+
+
+$cc = "";
+$time = time - scalar $#files;
+
+sub send_message
+{
+       my @recipients = unique_email_list(@to);
+       my $to = join (",\n\t", @recipients);
+       @recipients = unique_email_list(@recipients,@cc,@bcclist);
+       my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
+       my $gitversion = '@@GIT_VERSION@@';
+       if ($gitversion =~ m/..GIT_VERSION../) {
+           $gitversion = `git --version`;
+           chomp $gitversion;
+           # keep only what's after the last space
+           $gitversion =~ s/^.* //;
+       }
+
+       my $header = "From: $from
+To: $to
+Cc: $cc
+Subject: $subject
+Reply-To: $from
+Date: $date
+Message-Id: $message_id
+X-Mailer: git-send-email $gitversion
+";
+       if ($reply_to) {
+
+               $header .= "In-Reply-To: $reply_to\n";
+               $header .= "References: $references\n";
+       }
+
+       if ($smtp_server =~ m#^/#) {
+               my $pid = open my $sm, '|-';
+               defined $pid or die $!;
+               if (!$pid) {
+                       exec($smtp_server,'-i',
+                            map { extract_valid_address($_) }
+                            @recipients) or die $!;
+               }
+               print $sm "$header\n$message";
+               close $sm or die $?;
+       } else {
+               require Net::SMTP;
+               $smtp ||= Net::SMTP->new( $smtp_server );
+               $smtp->mail( $from ) or die $smtp->message;
+               $smtp->to( @recipients ) or die $smtp->message;
+               $smtp->data or die $smtp->message;
+               $smtp->datasend("$header\n$message") or die $smtp->message;
+               $smtp->dataend() or die $smtp->message;
+               $smtp->ok or die "Failed to send $subject\n".$smtp->message;
+       }
+       if ($quiet) {
+               printf "Sent %s\n", $subject;
+       } else {
+               print "OK. Log says:\nDate: $date\n";
+               if ($smtp) {
+                       print "Server: $smtp_server\n";
+               } else {
+                       print "Sendmail: $smtp_server\n";
+               }
+               print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               if ($smtp) {
+                       print "Result: ", $smtp->code, ' ',
+                               ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+               } else {
+                       print "Result: OK\n";
+               }
+       }
+}
+
+$reply_to = $initial_reply_to;
+$references = $initial_reply_to || '';
+make_message_id();
+$subject = $initial_subject;
+
+foreach my $t (@files) {
+       open(F,"<",$t) or die "can't open file $t";
+
+       my $author_not_sender = undef;
+       @cc = @initial_cc;
+       my $found_mbox = 0;
+       my $header_done = 0;
+       $message = "";
+       while(<F>) {
+               if (!$header_done) {
+                       $found_mbox = 1, next if (/^From /);
+                       chomp;
+
+                       if ($found_mbox) {
+                               if (/^Subject:\s+(.*)$/) {
+                                       $subject = $1;
+
+                               } elsif (/^(Cc|From):\s+(.*)$/) {
+                                       if ($2 eq $from) {
+                                               next if ($suppress_from);
+                                       }
+                                       else {
+                                               $author_not_sender = $2;
+                                       }
+                                       printf("(mbox) Adding cc: %s from line '%s'\n",
+                                               $2, $_) unless $quiet;
+                                       push @cc, $2;
+                               }
+
+                       } else {
+                               # In the traditional
+                               # "send lots of email" format,
+                               # line 1 = cc
+                               # line 2 = subject
+                               # So let's support that, too.
+                               if (@cc == 0) {
+                                       printf("(non-mbox) Adding cc: %s from line '%s'\n",
+                                               $_, $_) unless $quiet;
+
+                                       push @cc, $_;
+
+                               } elsif (!defined $subject) {
+                                       $subject = $_;
+                               }
+                       }
+
+                       # A whitespace line will terminate the headers
+                       if (m/^\s*$/) {
+                               $header_done = 1;
+                       }
+               } else {
+                       $message .=  $_;
+                       if (/^Signed-off-by: (.*)$/i && !$no_signed_off_cc) {
+                               my $c = $1;
+                               chomp $c;
+                               push @cc, $c;
+                               printf("(sob) Adding cc: %s from line '%s'\n",
+                                       $c, $_) unless $quiet;
+                       }
+               }
+       }
+       close F;
+       if (defined $author_not_sender) {
+               $message = "From: $author_not_sender\n\n$message";
+       }
+
+       $cc = join(", ", unique_email_list(@cc));
+
+       send_message();
+
+       # set up for the next message
+       if ($chain_reply_to || length($reply_to) == 0) {
+               $reply_to = $message_id;
+               if (length $references > 0) {
+                       $references .= " $message_id";
+               } else {
+                       $references = "$message_id";
+               }
+       }
+       make_message_id();
+}
+
+if ($compose) {
+       cleanup_compose_files();
+}
+
+sub cleanup_compose_files() {
+       unlink($compose_filename, $compose_filename . ".final");
+
+}
+
+$smtp->quit if $smtp;
+
+sub unique_email_list(@) {
+       my %seen;
+       my @emails;
+
+       foreach my $entry (@_) {
+               if (my $clean = extract_valid_address($entry)) {
+                       $seen{$clean} ||= 0;
+                       next if $seen{$clean}++;
+                       push @emails, $entry;
+               } else {
+                       print STDERR "W: unable to extract a valid address",
+                                       " from: $entry\n";
+               }
+       }
+       return @emails;
+}
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
new file mode 100755 (executable)
index 0000000..d15747f
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# This is included in commands that either have to be run from the toplevel
+# of the repository, or with GIT_DIR environment variable properly.
+# If the GIT_DIR does not look like the right correct git-repository,
+# it dies.
+
+# Having this variable in your environment would break scripts because
+# you would cause "cd" to be be taken to unexpected places.  If you
+# like CDPATH, define it for your interactive shell sessions without
+# exporting it.
+unset CDPATH
+
+die() {
+       echo >&2 "$@"
+       exit 1
+}
+
+usage() {
+       die "Usage: $0 $USAGE"
+}
+
+if [ -z "$LONG_USAGE" ]
+then
+       LONG_USAGE="Usage: $0 $USAGE"
+else
+       LONG_USAGE="Usage: $0 $USAGE
+
+$LONG_USAGE"
+fi
+
+case "$1" in
+       -h|--h|--he|--hel|--help)
+       echo "$LONG_USAGE"
+       exit
+esac
+
+if [ -z "$SUBDIRECTORY_OK" ]
+then
+       : ${GIT_DIR=.git}
+       : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+
+       # Make sure we are in a valid repository of a vintage we understand.
+       GIT_DIR="$GIT_DIR" git repo-config --get core.nosuch >/dev/null
+       if test $? = 128
+       then
+           exit
+       fi
+else
+       GIT_DIR=$(git-rev-parse --git-dir) || exit
+fi
diff --git a/git-shortlog.perl b/git-shortlog.perl
new file mode 100755 (executable)
index 0000000..0b14f83
--- /dev/null
@@ -0,0 +1,200 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+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 shortlog_output {
+       my ($obj, $key, $desc);
+
+       foreach $key (sort by_name keys %map) {
+               # output author
+               printf "%s:\n", $key;
+
+               # output author's 1-line summaries
+               $obj = $map{$key};
+               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;
+&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>
diff --git a/git-svnimport.perl b/git-svnimport.perl
new file mode 100755 (executable)
index 0000000..38ac732
--- /dev/null
@@ -0,0 +1,924 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to pull and analyze SVN changes.
+#
+# Checking out the files is done by a single long-running SVN connection.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Copy;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+use SVN::Core;
+use SVN::Ra;
+
+die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
+
+$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);
+
+sub usage() {
+       print STDERR <<END;
+Usage: ${\basename $0}     # fetch/update GIT from SVN
+       [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
+       [-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] [SVN_URL]
+END
+       exit(1);
+}
+
+getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage();
+usage if $opt_h;
+
+my $tag_name = $opt_t || "tags";
+my $trunk_name = $opt_T || "trunk";
+my $branch_name = $opt_b || "branches";
+
+@ARGV == 1 or @ARGV == 2 or usage();
+
+$opt_o ||= "origin";
+$opt_s ||= 1;
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $svn_url = $ARGV[0];
+my $svn_dir = $ARGV[1];
+
+our @mergerx = ();
+if ($opt_m) {
+       my $branch_esc = quotemeta ($branch_name);
+       my $trunk_esc  = quotemeta ($trunk_name);
+       @mergerx =
+       (
+               qr!\b(?:merg(?:ed?|ing))\b.*?\b((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+((?:(?<=$branch_esc/)[\w\.\-]+)|(?:$trunk_esc))\b!i,
+               qr!\b(?:from|of)\W+(?:the )?([\w\.\-]+)[-\s]branch\b!i
+       );
+}
+if ($opt_M) {
+       unshift (@mergerx, qr/$opt_M/);
+}
+
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
+our %users = ();
+our $users_file = undef;
+sub read_users($) {
+       $users_file = File::Spec->rel2abs(@_);
+       die "Cannot open $users_file\n" unless -f $users_file;
+       open(my $authors,$users_file);
+       while(<$authors>) {
+               chomp;
+               next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               (my $user,my $name,my $email) = ($1,$2,$3);
+               $users{$user} = [$name,$email];
+       }
+       close($authors);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package SVNconn;
+# Basic SVN connection.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+use Fcntl qw(SEEK_SET);
+
+sub new {
+       my($what,$repo) = @_;
+       $what=ref($what) if ref($what);
+
+       my $self = {};
+       $self->{'buffer'} = "";
+       bless($self,$what);
+
+       $repo =~ s#/+$##;
+       $self->{'fullrep'} = $repo;
+       $self->conn();
+
+       return $self;
+}
+
+sub conn {
+       my $self = shift;
+       my $repo = $self->{'fullrep'};
+       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 = SVN::Ra->new(url => $repo, auth => $auth);
+       die "SVN connection to $repo: $!\n" unless defined $s;
+       $self->{'svn'} = $s;
+       $self->{'repo'} = $repo;
+       $self->{'maxrev'} = $s->get_latest_revnum();
+}
+
+sub file {
+       my($self,$path,$rev) = @_;
+
+       my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                   DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+       print "... $rev $path ...\n" if $opt_v;
+       my (undef, $properties);
+       my $pool = SVN::Pool->new();
+       eval { (undef, $properties)
+                  = $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
+       $pool->clear;
+       if($@) {
+               return undef if $@ =~ /Attempted to get checksum/;
+               die $@;
+       }
+       my $mode;
+       if (exists $properties->{'svn:executable'}) {
+               $mode = '100755';
+       } elsif (exists $properties->{'svn:special'}) {
+               my ($special_content, $filesize);
+               $filesize = tell $fh;
+               seek $fh, 0, SEEK_SET;
+               read $fh, $special_content, $filesize;
+               if ($special_content =~ s/^link //) {
+                       $mode = '120000';
+                       seek $fh, 0, SEEK_SET;
+                       truncate $fh, 0;
+                       print $fh $special_content;
+               } else {
+                       die "unexpected svn:special file encountered";
+               }
+       } else {
+               $mode = '100644';
+       }
+       close ($fh);
+
+       return ($name, $mode);
+}
+
+sub ignore {
+       my($self,$path,$rev) = @_;
+
+       print "... $rev $path ...\n" if $opt_v;
+       my (undef,undef,$properties)
+           = $self->{'svn'}->get_dir($path,$rev,undef);
+       if (exists $properties->{'svn:ignore'}) {
+               my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                                          DIR => File::Spec->tmpdir(),
+                                          UNLINK => 1);
+               print $fh $properties->{'svn:ignore'};
+               close($fh);
+               return $name;
+       } else {
+               return undef;
+       }
+}
+
+package main;
+use URI;
+
+our $svn = $svn_url;
+$svn .= "/$svn_dir" if defined $svn_dir;
+my $svn2 = SVNconn->new($svn);
+$svn = SVNconn->new($svn);
+
+my $lwp_ua;
+if($opt_d or $opt_D) {
+       $svn_url = URI->new($svn_url)->canonical;
+       if($opt_D) {
+               $svn_dir =~ s#/*$#/#;
+       } else {
+               $svn_dir = "";
+       }
+       if ($svn_url->scheme eq "http") {
+               use LWP::UserAgent;
+               $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
+       } else {
+               print STDERR "Warning: not HTTP; turning off direct file access\n";
+               $opt_d=0;
+       }
+}
+
+sub pdate($) {
+       my($d) = @_;
+       $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
+               or die "Unparseable date: $d\n";
+       my $y=$1; $y-=1900 if $y>1900;
+       return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub getwd() {
+       my $pwd = `pwd`;
+       chomp $pwd;
+       return $pwd;
+}
+
+
+sub get_headref($$) {
+    my $name    = shift;
+    my $git_dir = shift;
+    my $sha;
+
+    if (open(C,"$git_dir/refs/heads/$name")) {
+       chomp($sha = <C>);
+       close(C);
+       length($sha) == 40
+           or die "Cannot get head id for $name ($sha): $!\n";
+    }
+    return $sha;
+}
+
+
+-d $git_tree
+       or mkdir($git_tree,0777)
+       or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $orig_branch = "";
+my $forward_master = 0;
+my %branches;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+                                   DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+my $maxnum = 0;
+my $last_rev = "";
+my $last_branch;
+my $current_rev = $opt_s || 1;
+unless(-d $git_dir) {
+       system("git-init-db");
+       die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+       system("git-read-tree");
+       die "Cannot init an empty tree: $?\n" if $?;
+
+       $last_branch = $opt_o;
+       $orig_branch = "";
+} else {
+       -f "$git_dir/refs/heads/$opt_o"
+               or die "Branch '$opt_o' does not exist.\n".
+                      "Either use the correct '-o branch' option,\n".
+                      "or import to a new repository.\n";
+
+       -f "$git_dir/svn2git"
+               or die "'$git_dir/svn2git' does not exist.\n".
+                      "You need that file for incremental imports.\n";
+       open(F, "git-symbolic-ref HEAD |") or
+               die "Cannot run git-symbolic-ref: $!\n";
+       chomp ($last_branch = <F>);
+       $last_branch = basename($last_branch);
+       close(F);
+       unless($last_branch) {
+               warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+               $last_branch = "master";
+       }
+       $orig_branch = $last_branch;
+       $last_rev = get_headref($orig_branch, $git_dir);
+       if (-f "$git_dir/SVN2GIT_HEAD") {
+               die <<EOM;
+SVN2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
+You may need to run
+
+    git-read-tree -m -u SVN2GIT_HEAD HEAD
+EOM
+       }
+       system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
+
+       $forward_master =
+           $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+           system('cmp', '-s', "$git_dir/refs/heads/master",
+                               "$git_dir/refs/heads/$opt_o") == 0;
+
+       # populate index
+       system('git-read-tree', $last_rev);
+       die "read-tree failed: $?\n" if $?;
+
+       # Get the last import timestamps
+       open my $B,"<", "$git_dir/svn2git";
+       while(<$B>) {
+               chomp;
+               my($num,$branch,$ref) = split;
+               $branches{$branch}{$num} = $ref;
+               $branches{$branch}{"LAST"} = $ref;
+               $current_rev = $num+1 if $current_rev <= $num;
+       }
+       close($B);
+}
+-d $git_dir
+       or die "Could not create git subdir ($git_dir).\n";
+
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+       read_users($opt_A);
+       copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+       read_users($default_authors) if -f $default_authors;
+}
+
+open BRANCHES,">>", "$git_dir/svn2git";
+
+sub node_kind($$$) {
+       my ($branch, $path, $revision) = @_;
+       my $pool=SVN::Pool->new;
+       my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+       $pool->clear;
+       return $kind;
+}
+
+sub revert_split_path($$) {
+       my($branch,$path) = @_;
+
+       my $svnpath;
+       $path = "" if $path eq "/"; # this should not happen, but ...
+       if($branch eq "/") {
+               $svnpath = "$trunk_name/$path";
+       } elsif($branch =~ m#^/#) {
+               $svnpath = "$tag_name$branch/$path";
+       } else {
+               $svnpath = "$branch_name/$branch/$path";
+       }
+
+       $svnpath =~ s#/+$##;
+       return $svnpath;
+}
+
+sub get_file($$$) {
+       my($rev,$branch,$path) = @_;
+
+       my $svnpath = revert_split_path($branch,$path);
+
+       # now get it
+       my ($name,$mode);
+       if($opt_d) {
+               my($req,$res);
+
+               # /svn/!svn/bc/2/django/trunk/django-docs/build.py
+               my $url=$svn_url->clone();
+               $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
+               print "... $path...\n" if $opt_v;
+               $req = HTTP::Request->new(GET => $url);
+               $res = $lwp_ua->request($req);
+               if ($res->is_success) {
+                       my $fh;
+                       ($fh, $name) = tempfile('gitsvn.XXXXXX',
+                       DIR => File::Spec->tmpdir(), UNLINK => 1);
+                       print $fh $res->content;
+                       close($fh) or die "Could not write $name: $!\n";
+               } else {
+                       return undef if $res->code == 301; # directory?
+                       die $res->status_line." at $url\n";
+               }
+               $mode = '0644'; # can't obtain mode via direct http request?
+       } else {
+               ($name,$mode) = $svn->file("$svnpath",$rev);
+               return undef unless defined $name;
+       }
+
+       my $pid = open(my $F, '-|');
+       die $! unless defined $pid;
+       if (!$pid) {
+           exec("git-hash-object", "-w", $name)
+               or die "Cannot create object: $!\n";
+       }
+       my $sha = <$F>;
+       chomp $sha;
+       close $F;
+       unlink $name;
+       return [$mode, $sha, $path];
+}
+
+sub get_ignore($$$$$) {
+       my($new,$old,$rev,$branch,$path) = @_;
+
+       return unless $opt_I;
+       my $svnpath = revert_split_path($branch,$path);
+       my $name = $svn->ignore("$svnpath",$rev);
+       if ($path eq '/') {
+               $path = $opt_I;
+       } else {
+               $path = File::Spec->catfile($path,$opt_I);
+       }
+       if (defined $name) {
+               my $pid = open(my $F, '-|');
+               die $! unless defined $pid;
+               if (!$pid) {
+                       exec("git-hash-object", "-w", $name)
+                           or die "Cannot create object: $!\n";
+               }
+               my $sha = <$F>;
+               chomp $sha;
+               close $F;
+               unlink $name;
+               push(@$new,['0644',$sha,$path]);
+       } else {
+               push(@$old,$path);
+       }
+}
+
+sub split_path($$) {
+       my($rev,$path) = @_;
+       my $branch;
+
+       if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
+               $branch = "/$1";
+       } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
+               $branch = "/";
+       } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
+               $branch = $1;
+       } else {
+               my %no_error = (
+                       "/" => 1,
+                       "/$tag_name" => 1,
+                       "/$branch_name" => 1
+               );
+               print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
+               return ()
+       }
+       $path = "/" if $path eq "";
+       return ($branch,$path);
+}
+
+sub branch_rev($$) {
+
+       my ($srcbranch,$uptorev) = @_;
+
+       my $bbranches = $branches{$srcbranch};
+       my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
+       my $therev;
+       foreach my $arev(@revs) {
+               next if  ($arev eq 'LAST');
+               if ($arev <= $uptorev) {
+                       $therev = $arev;
+                       last;
+               }
+       }
+       return $therev;
+}
+
+sub copy_path($$$$$$$$) {
+       # Somebody copied a whole subdirectory.
+       # We need to find the index entries from the old version which the
+       # SVN log entry points to, and add them to the new place.
+
+       my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
+
+       my($srcbranch,$srcpath) = split_path($rev,$oldpath);
+       unless(defined $srcbranch) {
+               print "Path not found when copying from $oldpath @ $rev\n";
+               return;
+       }
+       my $therev = branch_rev($srcbranch, $rev);
+       my $gitrev = $branches{$srcbranch}{$therev};
+       unless($gitrev) {
+               print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
+               return;
+       }
+       if ($srcbranch ne $newbranch) {
+               push(@$parents, $branches{$srcbranch}{'LAST'});
+       }
+       print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
+       if ($node_kind eq $SVN::Node::dir) {
+                       $srcpath =~ s#/*$#/#;
+       }
+       
+       my $pid = open my $f,'-|';
+       die $! unless defined $pid;
+       if (!$pid) {
+               exec("git-ls-tree","-r","-z",$gitrev,$srcpath)
+                       or die $!;
+       }
+       local $/ = "\0";
+       while(<$f>) {
+               chomp;
+               my($m,$p) = split(/\t/,$_,2);
+               my($mode,$type,$sha1) = split(/ /,$m);
+               next if $type ne "blob";
+               if ($node_kind eq $SVN::Node::dir) {
+                       $p = $path . substr($p,length($srcpath)-1);
+               } else {
+                       $p = $path;
+               }
+               push(@$new,[$mode,$sha1,$p]);   
+       }
+       close($f) or
+               print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
+}
+
+sub commit {
+       my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
+       my($author_name,$author_email,$dest);
+       my(@old,@new,@parents);
+
+       if (not defined $author) {
+               $author_name = $author_email = "unknown";
+       } elsif (defined $users_file) {
+               die "User $author is not listed in $users_file\n"
+                   unless exists $users{$author};
+               ($author_name,$author_email) = @{$users{$author}};
+       } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
+               ($author_name, $author_email) = ($1, $2);
+       } else {
+               $author =~ s/^<(.*)>$/$1/;
+               $author_name = $author_email = $author;
+       }
+       $date = pdate($date);
+
+       my $tag;
+       my $parent;
+       if($branch eq "/") { # trunk
+               $parent = $opt_o;
+       } elsif($branch =~ m#^/(.+)#) { # tag
+               $tag = 1;
+               $parent = $1;
+       } else { # "normal" branch
+               # nothing to do
+               $parent = $branch;
+       }
+       $dest = $parent;
+
+       my $prev = $changed_paths->{"/"};
+       if($prev and $prev->[0] eq "A") {
+               delete $changed_paths->{"/"};
+               my $oldpath = $prev->[1];
+               my $rev;
+               if(defined $oldpath) {
+                       my $p;
+                       ($parent,$p) = split_path($revision,$oldpath);
+                       if($parent eq "/") {
+                               $parent = $opt_o;
+                       } else {
+                               $parent =~ s#^/##; # if it's a tag
+                       }
+               } else {
+                       $parent = undef;
+               }
+       }
+
+       my $rev;
+       if($revision > $opt_s and defined $parent) {
+               open(H,"git-rev-parse --verify $parent |");
+               $rev = <H>;
+               close(H) or do {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               };
+               chop $rev;
+               if(length($rev) != 40) {
+                       print STDERR "$revision: cannot find commit '$parent'!\n";
+                       return;
+               }
+               $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
+               if($revision != $opt_s and not $rev) {
+                       print STDERR "$revision: do not know ancestor for '$parent'!\n";
+                       return;
+               }
+       } else {
+               $rev = undef;
+       }
+
+#      if($prev and $prev->[0] eq "A") {
+#              if(not $tag) {
+#                      unless(open(H,"> $git_dir/refs/heads/$branch")) {
+#                              print STDERR "$revision: Could not create branch $branch: $!\n";
+#                              $state=11;
+#                              next;
+#                      }
+#                      print H "$rev\n"
+#                              or die "Could not write branch $branch: $!";
+#                      close(H)
+#                              or die "Could not write branch $branch: $!";
+#              }
+#      }
+       if(not defined $rev) {
+               unlink($git_index);
+       } elsif ($rev ne $last_rev) {
+               print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
+               system("git-read-tree", $rev);
+               die "read-tree failed for $rev: $?\n" if $?;
+               $last_rev = $rev;
+       }
+
+       push (@parents, $rev) if defined $rev;
+
+       my $cid;
+       if($tag and not %$changed_paths) {
+               $cid = $rev;
+       } else {
+               my @paths = sort keys %$changed_paths;
+               foreach my $path(@paths) {
+                       my $action = $changed_paths->{$path};
+
+                       if ($action->[0] eq "R") {
+                               # refer to a file/tree in an earlier commit
+                               push(@old,$path); # remove any old stuff
+                       }
+                       if(($action->[0] eq "A") || ($action->[0] eq "R")) {
+                               my $node_kind = node_kind($branch,$path,$revision);
+                               if ($node_kind eq $SVN::Node::file) {
+                                       my $f = get_file($revision,$branch,$path);
+                                       if ($f) {
+                                               push(@new,$f) if $f;
+                                       } else {
+                                               my $opath = $action->[3];
+                                               print STDERR "$revision: $branch: could not fetch '$opath'\n";
+                                       }
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       if($action->[1]) {
+                                               copy_path($revision, $branch,
+                                                         $path, $action->[1],
+                                                         $action->[2], $node_kind,
+                                                         \@new, \@parents);
+                                       } else {
+                                               get_ignore(\@new, \@old, $revision,
+                                                          $branch, $path);
+                                       }
+                               }
+                       } elsif ($action->[0] eq "D") {
+                               push(@old,$path);
+                       } elsif ($action->[0] eq "M") {
+                               my $node_kind = node_kind($branch,$path,$revision);
+                               if ($node_kind eq $SVN::Node::file) {
+                                       my $f = get_file($revision,$branch,$path);
+                                       push(@new,$f) if $f;
+                               } elsif ($node_kind eq $SVN::Node::dir) {
+                                       get_ignore(\@new, \@old, $revision,
+                                                  $branch,$path);
+                               }
+                       } else {
+                               die "$revision: unknown action '".$action->[0]."' for $path\n";
+                       }
+               }
+
+               while(@old) {
+                       my @o1;
+                       if(@old > 55) {
+                               @o1 = splice(@old,0,50);
+                       } else {
+                               @o1 = @old;
+                               @old = ();
+                       }
+                       my $pid = open my $F, "-|";
+                       die "$!" unless defined $pid;
+                       if (!$pid) {
+                               exec("git-ls-files", "-z", @o1) or die $!;
+                       }
+                       @o1 = ();
+                       local $/ = "\0";
+                       while(<$F>) {
+                               chomp;
+                               push(@o1,$_);
+                       }
+                       close($F);
+
+                       while(@o1) {
+                               my @o2;
+                               if(@o1 > 55) {
+                                       @o2 = splice(@o1,0,50);
+                               } else {
+                                       @o2 = @o1;
+                                       @o1 = ();
+                               }
+                               system("git-update-index","--force-remove","--",@o2);
+                               die "Cannot remove files: $?\n" if $?;
+                       }
+               }
+               while(@new) {
+                       my @n2;
+                       if(@new > 12) {
+                               @n2 = splice(@new,0,10);
+                       } else {
+                               @n2 = @new;
+                               @new = ();
+                       }
+                       system("git-update-index","--add",
+                               (map { ('--cacheinfo', @$_) } @n2));
+                       die "Cannot add files: $?\n" if $?;
+               }
+
+               my $pid = open(C,"-|");
+               die "Cannot fork: $!" unless defined $pid;
+               unless($pid) {
+                       exec("git-write-tree");
+                       die "Cannot exec git-write-tree: $!\n";
+               }
+               chomp(my $tree = <C>);
+               length($tree) == 40
+                       or die "Cannot get tree id ($tree): $!\n";
+               close(C)
+                       or die "Error running git-write-tree: $?\n";
+               print "Tree ID $tree\n" if $opt_v;
+
+               my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+               $pid = fork();
+               die "Fork: $!\n" unless defined $pid;
+               unless($pid) {
+                       $pr->writer();
+                       $pw->reader();
+                       open(OUT,">&STDOUT");
+                       dup2($pw->fileno(),0);
+                       dup2($pr->fileno(),1);
+                       $pr->close();
+                       $pw->close();
+
+                       my @par = ();
+
+                       # loose detection of merges
+                       # based on the commit msg
+                       foreach my $rx (@mergerx) {
+                               if ($message =~ $rx) {
+                                       my $mparent = $1;
+                                       if ($mparent eq 'HEAD') { $mparent = $opt_o };
+                                       if ( -e "$git_dir/refs/heads/$mparent") {
+                                               $mparent = get_headref($mparent, $git_dir);
+                                               push (@parents, $mparent);
+                                               print OUT "Merge parent branch: $mparent\n" if $opt_v;
+                                       }
+                               }
+                       }
+                       my %seen_parents = ();
+                       my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
+                       foreach my $bparent (@unique_parents) {
+                               push @par, '-p', $bparent;
+                               print OUT "Merge parent branch: $bparent\n" if $opt_v;
+                       }
+
+                       exec("env",
+                               "GIT_AUTHOR_NAME=$author_name",
+                               "GIT_AUTHOR_EMAIL=$author_email",
+                               "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "GIT_COMMITTER_NAME=$author_name",
+                               "GIT_COMMITTER_EMAIL=$author_email",
+                               "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+                               "git-commit-tree", $tree,@par);
+                       die "Cannot exec git-commit-tree: $!\n";
+               }
+               $pw->writer();
+               $pr->reader();
+
+               $message =~ s/[\s\n]+\z//;
+               $message = "r$revision: $message" if $opt_r;
+
+               print $pw "$message\n"
+                       or die "Error writing to git-commit-tree: $!\n";
+               $pw->close();
+
+               print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+               chomp($cid = <$pr>);
+               length($cid) == 40
+                       or die "Cannot get commit id ($cid): $!\n";
+               print "Commit ID $cid\n" if $opt_v;
+               $pr->close();
+
+               waitpid($pid,0);
+               die "Error running git-commit-tree: $?\n" if $?;
+       }
+
+       if (not defined $cid) {
+               $cid = $branches{"/"}{"LAST"};
+       }
+
+       if(not defined $dest) {
+               print "... no known parent\n" if $opt_v;
+       } elsif(not $tag) {
+               print "Writing to refs/heads/$dest\n" if $opt_v;
+               open(C,">$git_dir/refs/heads/$dest") and
+               print C ("$cid\n") and
+               close(C)
+                       or die "Cannot write branch $dest for update: $!\n";
+       }
+
+       if($tag) {
+               my($in, $out) = ('','');
+               $last_rev = "-" if %$changed_paths;
+               # the tag was 'complex', i.e. did not refer to a "real" revision
+
+               $dest =~ tr/_/\./ if $opt_u;
+               $branch = $dest;
+
+               my $pid = open2($in, $out, 'git-mktag');
+               print $out ("object $cid\n".
+                   "type commit\n".
+                   "tag $dest\n".
+                   "tagger $author_name <$author_email>\n") and
+               close($out)
+                   or die "Cannot create tag object $dest: $!\n";
+
+               my $tagobj = <$in>;
+               chomp $tagobj;
+
+               if ( !close($in) or waitpid($pid, 0) != $pid or
+                               $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+                       die "Cannot create tag object $dest: $!\n";
+               }
+
+               open(C,">$git_dir/refs/tags/$dest") and
+               print C ("$tagobj\n") and
+               close(C)
+                       or die "Cannot create tag $branch: $!\n";
+
+               print "Created tag '$dest' on '$branch'\n" if $opt_v;
+       }
+       $branches{$branch}{"LAST"} = $cid;
+       $branches{$branch}{$revision} = $cid;
+       $last_rev = $cid;
+       print BRANCHES "$revision $branch $cid\n";
+       print "DONE: $revision $dest $cid\n" if $opt_v;
+}
+
+sub commit_all {
+       # Recursive use of the SVN connection does not work
+       local $svn = $svn2;
+
+       my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+       my %p;
+       while(my($path,$action) = each %$changed_paths) {
+               $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
+       }
+       $changed_paths = \%p;
+
+       my %done;
+       my @col;
+       my $pref;
+       my $branch;
+
+       while(my($path,$action) = each %$changed_paths) {
+               ($branch,$path) = split_path($revision,$path);
+               next if not defined $branch;
+               $done{$branch}{$path} = $action;
+       }
+       while(($branch,$changed_paths) = each %done) {
+               commit($branch, $changed_paths, $revision, $author, $date, $message);
+       }
+}
+
+$opt_l = $svn->{'maxrev'} if not defined $opt_l or $opt_l > $svn->{'maxrev'};
+
+if ($opt_l < $current_rev) {
+    print "Up to date: no new revisions to fetch!\n" if $opt_v;
+    unlink("$git_dir/SVN2GIT_HEAD");
+    exit;
+}
+
+print "Fetching 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;
+
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+       $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+       delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+       print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               if $forward_master;
+       unless ($opt_i) {
+               system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+               die "read-tree failed: $?\n" if $?;
+       }
+} else {
+       $orig_branch = "master";
+       print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+       system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+               unless -f "$git_dir/refs/heads/master";
+       system('git-update-ref', 'HEAD', "$orig_branch");
+       unless ($opt_i) {
+               system('git checkout');
+               die "checkout failed: $?\n" if $?;
+       }
+}
+unlink("$git_dir/SVN2GIT_HEAD");
+close(BRANCHES);
diff --git a/git-tag.sh b/git-tag.sh
new file mode 100755 (executable)
index 0000000..a0afa25
--- /dev/null
@@ -0,0 +1,112 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+annotate=
+signed=
+force=
+message=
+username=
+list=
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -a)
+       annotate=1
+       ;;
+    -s)
+       annotate=1
+       signed=1
+       ;;
+    -f)
+       force=1
+       ;;
+    -l)
+       case "$#" in
+       1)
+               set x . ;;
+       esac
+       shift
+       git rev-parse --symbolic --tags | sort | grep "$@"
+       exit $?
+       ;;
+    -m)
+       annotate=1
+       shift
+       message="$1"
+       ;;
+    -u)
+       annotate=1
+       signed=1
+       shift
+       username="$1"
+       ;;
+    -d)
+       shift
+       tag_name="$1"
+       rm "$GIT_DIR/refs/tags/$tag_name" && \
+               echo "Deleted tag $tag_name."
+       exit $?
+       ;;
+    -*)
+        usage
+       ;;
+    *)
+       break
+       ;;
+    esac
+    shift
+done
+
+name="$1"
+[ "$name" ] || usage
+if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
+    die "tag '$name' already exists"
+fi
+shift
+git-check-ref-format "tags/$name" ||
+       die "we do not like '$name' as a tag name."
+
+object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git-cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+: ${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
+        ( echo "#"
+          echo "# Write a tag message"
+          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
+        ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR"/TAG_EDITMSG || exit
+    else
+        echo "$message" >"$GIT_DIR"/TAG_EDITMSG
+    fi
+
+    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
+    git-stripspace >"$GIT_DIR"/TAG_FINALMSG
+
+    [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+       echo >&2 "No tag message?"
+       exit 1
+    }
+
+    ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
+       "$object" "$type" "$name" "$tagger";
+      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
+    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
+    if [ "$signed" ]; then
+       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
+       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
+       die "failed to sign the tag with GPG."
+    fi
+    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
+fi
+
+leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
+mkdir -p "$GIT_DIR/$leading" &&
+echo $object > "$GIT_DIR/refs/tags/$name"
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
new file mode 100755 (executable)
index 0000000..36f171b
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+USAGE='<tag>'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+verbose=
+while case $# in 0) break;; esac
+do
+       case "$1" in
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t ;;
+       *)
+               break ;;
+       esac
+       shift
+done
+
+if [ "$#" != "1" ]
+then
+       usage
+fi
+
+type="$(git-cat-file -t "$1" 2>/dev/null)" ||
+       die "$1: no such object."
+
+test "$type" = tag ||
+       die "$1: cannot verify a non-tag object of type $type."
+
+case "$verbose" in
+t)
+       git-cat-file -p "$1" |
+       sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
+       ;;
+esac
+
+git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
+cat "$GIT_DIR/.tmp-vtag" |
+sed '/-----BEGIN PGP/Q' |
+gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
+rm -f "$GIT_DIR/.tmp-vtag"
+
diff --git a/git.c b/git.c
new file mode 100644 (file)
index 0000000..6db8f2b
--- /dev/null
+++ b/git.c
@@ -0,0 +1,313 @@
+#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 "exec_cmd.h"
+#include "cache.h"
+
+#include "builtin.h"
+
+static void prepend_to_path(const char *dir, int len)
+{
+       char *path, *old_path = getenv("PATH");
+       int path_len = len;
+
+       if (!old_path)
+               old_path = "/usr/local/bin:/usr/bin:/bin";
+
+       path_len = len + strlen(old_path) + 1;
+
+       path = malloc(path_len + 1);
+
+       memcpy(path, dir, len);
+       path[len] = ':';
+       memcpy(path + len + 1, old_path, path_len - len);
+
+       setenv("PATH", path, 1);
+}
+
+static const char *alias_command;
+static char *alias_string = NULL;
+
+static int git_alias_config(const char *var, const char *value)
+{
+       if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) {
+               alias_string = strdup(value);
+       }
+       return 0;
+}
+
+static int split_cmdline(char *cmdline, const char ***argv)
+{
+       int src, dst, count = 0, size = 16;
+       char quoted = 0;
+
+       *argv = malloc(sizeof(char*) * size);
+
+       /* split alias_string */
+       (*argv)[count++] = cmdline;
+       for (src = dst = 0; cmdline[src];) {
+               char c = cmdline[src];
+               if (!quoted && isspace(c)) {
+                       cmdline[dst++] = 0;
+                       while (cmdline[++src]
+                                       && isspace(cmdline[src]))
+                               ; /* skip */
+                       if (count >= size) {
+                               size += 16;
+                               *argv = realloc(*argv, sizeof(char*) * size);
+                       }
+                       (*argv)[count++] = cmdline + dst;
+               } else if(!quoted && (c == '\'' || c == '"')) {
+                       quoted = c;
+                       src++;
+               } else if (c == quoted) {
+                       quoted = 0;
+                       src++;
+               } else {
+                       if (c == '\\' && quoted != '\'') {
+                               src++;
+                               c = cmdline[src];
+                               if (!c) {
+                                       free(*argv);
+                                       *argv = NULL;
+                                       return error("cmdline ends with \\");
+                               }
+                       }
+                       cmdline[dst++] = c;
+                       src++;
+               }
+       }
+
+       cmdline[dst] = 0;
+
+       if (quoted) {
+               free(*argv);
+               *argv = NULL;
+               return error("unclosed quote");
+       }
+
+       return count;
+}
+
+static int handle_alias(int *argcp, const char ***argv)
+{
+       int nongit = 0, ret = 0;
+       const char *subdir;
+
+       subdir = setup_git_directory_gently(&nongit);
+       if (!nongit) {
+               int count;
+               const char** new_argv;
+
+               alias_command = (*argv)[0];
+               git_config(git_alias_config);
+               if (alias_string) {
+
+                       count = split_cmdline(alias_string, &new_argv);
+
+                       if (count < 1)
+                               die("empty alias for %s", alias_command);
+
+                       if (!strcmp(alias_command, new_argv[0]))
+                               die("recursive alias: %s", alias_command);
+
+                       /* insert after command name */
+                       if (*argcp > 1) {
+                               new_argv = realloc(new_argv, sizeof(char*) *
+                                               (count + *argcp - 1));
+                               memcpy(new_argv + count, *argv, sizeof(char*) *
+                                               (*argcp - 1));
+                       }
+
+                       *argv = new_argv;
+                       *argcp += count - 1;
+
+                       ret = 1;
+               }
+       }
+
+       if (subdir)
+               chdir(subdir);
+
+       return ret;
+}
+
+const char git_version_string[] = GIT_VERSION;
+
+static void handle_internal_command(int argc, const char **argv, char **envp)
+{
+       const char *cmd = argv[0];
+       static struct cmd_struct {
+               const char *cmd;
+               int (*fn)(int, const char **, char **);
+       } commands[] = {
+               { "version", cmd_version },
+               { "help", cmd_help },
+               { "log", cmd_log },
+               { "whatchanged", cmd_whatchanged },
+               { "show", cmd_show },
+               { "push", cmd_push },
+               { "format-patch", cmd_format_patch },
+               { "count-objects", cmd_count_objects },
+               { "diff", cmd_diff },
+               { "grep", cmd_grep },
+               { "rm", cmd_rm },
+               { "add", cmd_add },
+               { "rev-list", cmd_rev_list },
+               { "init-db", cmd_init_db },
+               { "tar-tree", cmd_tar_tree },
+               { "upload-tar", cmd_upload_tar },
+               { "check-ref-format", cmd_check_ref_format },
+               { "ls-files", cmd_ls_files },
+               { "ls-tree", cmd_ls_tree },
+               { "tar-tree", cmd_tar_tree },
+               { "read-tree", cmd_read_tree },
+               { "commit-tree", cmd_commit_tree },
+               { "apply", cmd_apply },
+               { "show-branch", cmd_show_branch },
+               { "diff-files", cmd_diff_files },
+               { "diff-index", cmd_diff_index },
+               { "diff-stages", cmd_diff_stages },
+               { "diff-tree", cmd_diff_tree },
+               { "cat-file", cmd_cat_file },
+               { "rev-parse", cmd_rev_parse }
+       };
+       int i;
+
+       /* Turn "git cmd --help" into "git help cmd" */
+       if (argc > 1 && !strcmp(argv[1], "--help")) {
+               argv[1] = argv[0];
+               argv[0] = cmd = "help";
+       }
+
+       for (i = 0; i < ARRAY_SIZE(commands); i++) {
+               struct cmd_struct *p = commands+i;
+               if (strcmp(p->cmd, cmd))
+                       continue;
+               exit(p->fn(argc, argv, envp));
+       }
+}
+
+int main(int argc, const char **argv, char **envp)
+{
+       const char *cmd = argv[0];
+       char *slash = strrchr(cmd, '/');
+       char git_command[PATH_MAX + 1];
+       const char *exec_path = NULL;
+       int done_alias = 0;
+
+       /*
+        * Take the basename of argv[0] as the command
+        * name, and the dirname as the default exec_path
+        * if it's an absolute path and we don't have
+        * anything better.
+        */
+       if (slash) {
+               *slash++ = 0;
+               if (*cmd == '/')
+                       exec_path = cmd;
+               cmd = slash;
+       }
+
+       /*
+        * "git-xxxx" is the same as "git xxxx", but we obviously:
+        *
+        *  - cannot take flags in between the "git" and the "xxxx".
+        *  - cannot execute it externally (since it would just do
+        *    the same thing over again)
+        *
+        * So we just directly call the internal command handler, and
+        * die if that one cannot handle it.
+        */
+       if (!strncmp(cmd, "git-", 4)) {
+               cmd += 4;
+               argv[0] = cmd;
+               handle_internal_command(argc, argv, envp);
+               die("cannot handle %s internally", cmd);
+       }
+
+       /* Default command: "help" */
+       cmd = "help";
+
+       /* Look for flags.. */
+       while (argc > 1) {
+               cmd = *++argv;
+               argc--;
+
+               if (strncmp(cmd, "--", 2))
+                       break;
+
+               cmd += 2;
+
+               /*
+                * For legacy reasons, the "version" and "help"
+                * commands can be written with "--" prepended
+                * to make them look like flags.
+                */
+               if (!strcmp(cmd, "help"))
+                       break;
+               if (!strcmp(cmd, "version"))
+                       break;
+
+               /*
+                * Check remaining flags (which by now must be
+                * "--exec-path", but maybe we will accept
+                * other arguments some day)
+                */
+               if (!strncmp(cmd, "exec-path", 9)) {
+                       cmd += 9;
+                       if (*cmd == '=') {
+                               git_set_exec_path(cmd + 1);
+                               continue;
+                       }
+                       puts(git_exec_path());
+                       exit(0);
+               }
+               cmd_usage(0, NULL, NULL);
+       }
+       argv[0] = cmd;
+
+       /*
+        * We search for git commands in the following order:
+        *  - git_exec_path()
+        *  - the path of the "git" command if we could find it
+        *    in $0
+        *  - the regular PATH.
+        */
+       if (exec_path)
+               prepend_to_path(exec_path, strlen(exec_path));
+       exec_path = git_exec_path();
+       prepend_to_path(exec_path, strlen(exec_path));
+
+       while (1) {
+               /* See if it's an internal command */
+               handle_internal_command(argc, argv, envp);
+
+               /* .. then try the external ones */
+               execv_git_cmd(argv);
+
+               /* It could be an alias -- this works around the insanity
+                * of overriding "git log" with "git show" by having
+                * alias.log = show
+                */
+               if (done_alias || !handle_alias(&argc, &argv))
+                       break;
+               done_alias = 1;
+       }
+
+       if (errno == ENOENT)
+               cmd_usage(0, exec_path, "'%s' is not a git-command", cmd);
+
+       fprintf(stderr, "Failed to run command '%s': %s\n",
+               git_command, strerror(errno));
+
+       return 1;
+}
diff --git a/git.spec.in b/git.spec.in
new file mode 100644 (file)
index 0000000..8ccd256
--- /dev/null
@@ -0,0 +1,188 @@
+# Pass --without docs to rpmbuild if you don't want the documentation
+Name:          git
+Version:       @@VERSION@@
+Release:       1%{?dist}
+Summary:       Git core and tools
+License:       GPL
+Group:                 Development/Tools
+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
+
+%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.
+
+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
+%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.
+
+These are the core tools with minimal dependencies.
+
+%package svn
+Summary:        Git tools for importing Subversion repositories
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, subversion
+%description svn
+Git tools for importing Subversion repositories.
+
+%package cvs
+Summary:        Git tools for importing CVS repositories
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, cvs, cvsps
+%description cvs
+Git tools for importing CVS repositories.
+
+%package arch
+Summary:        Git tools for importing Arch repositories
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, tla
+%description arch
+Git tools for importing Arch repositories.
+
+%package email
+Summary:        Git tools for sending email
+Group:          Development/Tools
+Requires:      git-core = %{version}-%{release} 
+%description email
+Git tools for sending email.
+
+%package -n gitk
+Summary:        Git revision tree visualiser ('gitk')
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, tk >= 8.4
+%description -n gitk
+Git revision tree visualiser ('gitk')
+
+%prep
+%setup -q
+
+%build
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
+     prefix=%{_prefix} all %{!?_without_docs: doc}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
+     prefix=%{_prefix} mandir=%{_mandir} \
+     install %{!?_without_docs: install-doc}
+
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+%if %{!?_without_docs:1}0
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+# These are no files in the root package
+
+%files svn
+%defattr(-,root,root)
+%{_bindir}/*svn*
+%doc Documentation/*svn*.txt
+%{!?_without_docs: %{_mandir}/man1/*svn*.1*}
+%{!?_without_docs: %doc Documentation/*svn*.html }
+
+%files cvs
+%defattr(-,root,root)
+%doc Documentation/*git-cvs*.txt
+%{_bindir}/*cvs*
+%{!?_without_docs: %{_mandir}/man1/*cvs*.1*}
+%{!?_without_docs: %doc Documentation/*git-cvs*.html }
+
+%files arch
+%defattr(-,root,root)
+%doc Documentation/*arch*.txt
+%{_bindir}/*arch*
+%{!?_without_docs: %{_mandir}/man1/*arch*.1*}
+%{!?_without_docs: %doc Documentation/*arch*.html }
+
+%files email
+%defattr(-,root,root)
+%doc Documentation/*email*.txt
+%{_bindir}/*email*
+%{!?_without_docs: %{_mandir}/man1/*email*.1*}
+%{!?_without_docs: %doc Documentation/*email*.html }
+
+%files -n gitk
+%defattr(-,root,root)
+%doc Documentation/*gitk*.txt
+%{_bindir}/*gitk*
+%{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
+%{!?_without_docs: %doc Documentation/*gitk*.html }
+
+%files core -f bin-man-doc-files
+%defattr(-,root,root)
+%{_datadir}/git-core/
+%doc README COPYING Documentation/*.txt
+%{!?_without_docs: %doc Documentation/*.html }
+
+%changelog
+* 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
+- Rename git-tk -> gitk
+
+* Thu Nov 10 2005 Chris Wright <chrisw@osdl.org> 0.99.9g-1
+- zlib dependency fix
+- Minor cleanups from split
+- Move arch import to separate package as well
+
+* Tue Sep 27 2005 Jim Radford <radford@blackbean.org>
+- Move programs with non-standard dependencies (svn, cvs, email)
+  into separate packages
+
+* Tue Sep 27 2005 H. Peter Anvin <hpa@zytor.com>
+- parallelize build
+- COPTS -> CFLAGS
+
+* Fri Sep 16 2005 Chris Wright <chrisw@osdl.org> 0.99.6-1
+- update to 0.99.6
+
+* Fri Sep 16 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Linus noticed that less is required, added to the dependencies
+
+* Sun Sep 11 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Updated dependencies
+- Don't assume manpages are gzipped
+
+* Thu Aug 18 2005 Chris Wright <chrisw@osdl.org> 0.99.4-4
+- drop sh_utils, sh-utils, diffutils, mktemp, and openssl Requires
+- use RPM_OPT_FLAGS in spec file, drop patch0
+
+* Wed Aug 17 2005 Tom "spot" Callaway <tcallawa@redhat.com> 0.99.4-3
+- use dist tag to differentiate between branches
+- use rpm optflags by default (patch0)
+- own %{_datadir}/git-core/
+
+* Mon Aug 15 2005 Chris Wright <chrisw@osdl.org>
+- update spec file to fix Buildroot, Requires, and drop Vendor
+
+* Sun Aug 07 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Redid the description
+- Cut overlong make line, loosened changelog a bit
+- I think Junio (or perhaps OSDL?) should be vendor...
+
+* Thu Jul 14 2005 Eric Biederman <ebiederm@xmission.com>
+- Add the man pages, and the --without docs build option
+
+* Wed Jul 7 2005 Chris Wright <chris@osdl.org>
+- initial git spec file
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
new file mode 100644 (file)
index 0000000..fdbf9e4
--- /dev/null
@@ -0,0 +1,275 @@
+#
+# 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/hash-object.c b/hash-object.c
new file mode 100644 (file)
index 0000000..43bd93b
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+
+static void hash_object(const char *path, const char *type, int write_object)
+{
+       int fd;
+       struct stat st;
+       unsigned char sha1[20];
+       fd = open(path, O_RDONLY);
+       if (fd < 0 ||
+           fstat(fd, &st) < 0 ||
+           index_fd(sha1, fd, &st, write_object, type))
+               die(write_object
+                   ? "Unable to add %s to database"
+                   : "Unable to hash %s", path);
+       printf("%s\n", sha1_to_hex(sha1));
+}
+
+static void hash_stdin(const char *type, int write_object)
+{
+       unsigned char sha1[20];
+       if (index_pipe(sha1, 0, type, write_object))
+               die("Unable to add stdin to database");
+       printf("%s\n", sha1_to_hex(sha1));
+}
+
+static const char hash_object_usage[] =
+"git-hash-object [-t <type>] [-w] [--stdin] <file>...";
+
+int main(int argc, char **argv)
+{
+       int i;
+       const char *type = blob_type;
+       int write_object = 0;
+       const char *prefix = NULL;
+       int prefix_length = -1;
+       int no_more_flags = 0;
+
+       for (i = 1 ; i < argc; i++) {
+               if (!no_more_flags && argv[i][0] == '-') {
+                       if (!strcmp(argv[i], "-t")) {
+                               if (argc <= ++i)
+                                       die(hash_object_usage);
+                               type = argv[i];
+                       }
+                       else if (!strcmp(argv[i], "-w")) {
+                               if (prefix_length < 0) {
+                                       prefix = setup_git_directory();
+                                       prefix_length =
+                                               prefix ? strlen(prefix) : 0;
+                               }
+                               write_object = 1;
+                       }
+                       else if (!strcmp(argv[i], "--")) {
+                               no_more_flags = 1;
+                       }
+                       else if (!strcmp(argv[i], "--help"))
+                               usage(hash_object_usage);
+                       else if (!strcmp(argv[i], "--stdin")) {
+                               hash_stdin(type, write_object);
+                       }
+                       else
+                               die(hash_object_usage);
+               } 
+               else {
+                       const char *arg = argv[i];
+                       if (0 <= prefix_length)
+                               arg = prefix_filename(prefix, prefix_length,
+                                                     arg);
+                       hash_object(arg, type, write_object);
+                       no_more_flags = 1;
+               }
+       }
+       return 0;
+}
diff --git a/http-fetch.c b/http-fetch.c
new file mode 100644 (file)
index 0000000..d3602b7
--- /dev/null
@@ -0,0 +1,1286 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "fetch.h"
+#include "http.h"
+
+#ifndef NO_EXPAT
+#include <expat.h>
+
+/* Definitions for DAV requests */
+#define DAV_PROPFIND "PROPFIND"
+#define DAV_PROPFIND_RESP ".multistatus.response"
+#define DAV_PROPFIND_NAME ".multistatus.response.href"
+#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
+#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
+
+/* Definitions for processing XML DAV responses */
+#ifndef XML_STATUS_OK
+enum XML_Status {
+  XML_STATUS_OK = 1,
+  XML_STATUS_ERROR = 0
+};
+#define XML_STATUS_OK    1
+#define XML_STATUS_ERROR 0
+#endif
+
+/* Flags that control remote_ls processing */
+#define PROCESS_FILES (1u << 0)
+#define PROCESS_DIRS  (1u << 1)
+#define RECURSIVE     (1u << 2)
+
+/* Flags that remote_ls passes to callback functions */
+#define IS_DIR (1u << 0)
+#endif
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
+static int got_alternates = -1;
+static int corrupt_object_found = 0;
+
+static struct curl_slist *no_pragma_header;
+
+struct alt_base
+{
+       char *base;
+       int path_len;
+       int got_indices;
+       struct packed_git *packs;
+       struct alt_base *next;
+};
+
+static struct alt_base *alt = NULL;
+
+enum object_request_state {
+       WAITING,
+       ABORTED,
+       ACTIVE,
+       COMPLETE,
+};
+
+struct object_request
+{
+       unsigned char sha1[20];
+       struct alt_base *repo;
+       char *url;
+       char filename[PATH_MAX];
+       char tmpfile[PATH_MAX];
+       int local;
+       enum object_request_state state;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       struct active_request_slot *slot;
+       struct object_request *next;
+};
+
+struct alternates_request {
+       char *base;
+       char *url;
+       struct buffer *buffer;
+       struct active_request_slot *slot;
+       int http_specific;
+};
+
+#ifndef NO_EXPAT
+struct xml_ctx
+{
+       char *name;
+       int len;
+       char *cdata;
+       void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
+       void *userData;
+};
+
+struct remote_ls_ctx
+{
+       struct alt_base *repo;
+       char *path;
+       void (*userFunc)(struct remote_ls_ctx *ls);
+       void *userData;
+       int flags;
+       char *dentry_name;
+       int dentry_flags;
+       int rc;
+       struct remote_ls_ctx *parent;
+};
+#endif
+
+static struct object_request *object_queue_head = NULL;
+
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+                              void *data)
+{
+       unsigned char expn[4096];
+       size_t size = eltsize * nmemb;
+       int posn = 0;
+       struct object_request *obj_req = (struct object_request *)data;
+       do {
+               ssize_t retval = write(obj_req->local,
+                                      ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       obj_req->stream.avail_in = size;
+       obj_req->stream.next_in = ptr;
+       do {
+               obj_req->stream.next_out = expn;
+               obj_req->stream.avail_out = sizeof(expn);
+               obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);
+               SHA1_Update(&obj_req->c, expn,
+                           sizeof(expn) - obj_req->stream.avail_out);
+       } while (obj_req->stream.avail_in && obj_req->zret == Z_OK);
+       data_received++;
+       return size;
+}
+
+static void fetch_alternates(char *base);
+
+static void process_object_response(void *callback_data);
+
+static void start_object_request(struct object_request *obj_req)
+{
+       char *hex = sha1_to_hex(obj_req->sha1);
+       char prevfile[PATH_MAX];
+       char *url;
+       char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct active_request_slot *slot;
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
+       unlink(prevfile);
+       rename(obj_req->tmpfile, prevfile);
+       unlink(obj_req->tmpfile);
+
+       if (obj_req->local != -1)
+               error("fd leakage in start: %d", obj_req->local);
+       obj_req->local = open(obj_req->tmpfile,
+                             O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /* This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (obj_req->local < 0 && errno == ENOENT) {
+               char *dir = strrchr(obj_req->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(obj_req->tmpfile, 0777);
+                       *dir = '/';
+               }
+               obj_req->local = open(obj_req->tmpfile,
+                                     O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (obj_req->local < 0) {
+               obj_req->state = ABORTED;
+               error("Couldn't create temporary file %s for %s: %s",
+                     obj_req->tmpfile, obj_req->filename, strerror(errno));
+               return;
+       }
+
+       memset(&obj_req->stream, 0, sizeof(obj_req->stream));
+
+       inflateInit(&obj_req->stream);
+
+       SHA1_Init(&obj_req->c);
+
+       url = xmalloc(strlen(obj_req->repo->base) + 50);
+       obj_req->url = xmalloc(strlen(obj_req->repo->base) + 50);
+       strcpy(url, obj_req->repo->base);
+       posn = url + strlen(obj_req->repo->base);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       strcpy(obj_req->url, url);
+
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    obj_req) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&obj_req->stream, 0, sizeof(obj_req->stream));
+               inflateInit(&obj_req->stream);
+               SHA1_Init(&obj_req->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(obj_req->local, SEEK_SET, 0);
+                       ftruncate(obj_req->local, 0);
+               }
+       }
+
+       slot = get_active_slot();
+       slot->callback_func = process_object_response;
+       slot->callback_data = obj_req;
+       obj_req->slot = slot;
+
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       obj_req->state = ACTIVE;
+       if (!start_active_slot(slot)) {
+               obj_req->state = ABORTED;
+               obj_req->slot = NULL;
+               close(obj_req->local); obj_req->local = -1;
+               free(obj_req->url);
+               return;
+       }
+}
+
+static void finish_object_request(struct object_request *obj_req)
+{
+       struct stat st;
+
+       fchmod(obj_req->local, 0444);
+       close(obj_req->local); obj_req->local = -1;
+
+       if (obj_req->http_code == 416) {
+               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+       } else if (obj_req->curl_result != CURLE_OK) {
+               if (stat(obj_req->tmpfile, &st) == 0)
+                       if (st.st_size == 0)
+                               unlink(obj_req->tmpfile);
+               return;
+       }
+
+       inflateEnd(&obj_req->stream);
+       SHA1_Final(obj_req->real_sha1, &obj_req->c);
+       if (obj_req->zret != Z_STREAM_END) {
+               unlink(obj_req->tmpfile);
+               return;
+       }
+       if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+               unlink(obj_req->tmpfile);
+               return;
+       }
+       obj_req->rename =
+               move_temp_to_file(obj_req->tmpfile, obj_req->filename);
+
+       if (obj_req->rename == 0)
+               pull_say("got %s\n", sha1_to_hex(obj_req->sha1));
+}
+
+static void process_object_response(void *callback_data)
+{
+       struct object_request *obj_req =
+               (struct object_request *)callback_data;
+
+       obj_req->curl_result = obj_req->slot->curl_result;
+       obj_req->http_code = obj_req->slot->http_code;
+       obj_req->slot = NULL;
+       obj_req->state = COMPLETE;
+
+       /* Use alternates if necessary */
+       if (obj_req->http_code == 404 ||
+           obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+               fetch_alternates(alt->base);
+               if (obj_req->repo->next != NULL) {
+                       obj_req->repo =
+                               obj_req->repo->next;
+                       close(obj_req->local);
+                       obj_req->local = -1;
+                       start_object_request(obj_req);
+                       return;
+               }
+       }
+
+       finish_object_request(obj_req);
+}
+
+static void release_object_request(struct object_request *obj_req)
+{
+       struct object_request *entry = object_queue_head;
+
+       if (obj_req->local != -1)
+               error("fd leakage in release: %d", obj_req->local);
+       if (obj_req == object_queue_head) {
+               object_queue_head = obj_req->next;
+       } else {
+               while (entry->next != NULL && entry->next != obj_req)
+                       entry = entry->next;
+               if (entry->next == obj_req)
+                       entry->next = entry->next->next;
+       }
+
+       free(obj_req->url);
+       free(obj_req);
+}
+
+#ifdef USE_CURL_MULTI
+void fill_active_slots(void)
+{
+       struct object_request *obj_req = object_queue_head;
+       struct active_request_slot *slot = active_queue_head;
+       int num_transfers;
+
+       while (active_requests < max_requests && obj_req != NULL) {
+               if (obj_req->state == WAITING) {
+                       if (has_sha1_file(obj_req->sha1))
+                               obj_req->state = COMPLETE;
+                       else
+                               start_object_request(obj_req);
+                       curl_multi_perform(curlm, &num_transfers);
+               }
+               obj_req = obj_req->next;
+       }
+
+       while (slot != NULL) {
+               if (!slot->in_use && slot->curl != NULL) {
+                       curl_easy_cleanup(slot->curl);
+                       slot->curl = NULL;
+               }
+               slot = slot->next;
+       }
+}
+#endif
+
+void prefetch(unsigned char *sha1)
+{
+       struct object_request *newreq;
+       struct object_request *tail;
+       char *filename = sha1_file_name(sha1);
+
+       newreq = xmalloc(sizeof(*newreq));
+       memcpy(newreq->sha1, sha1, 20);
+       newreq->repo = alt;
+       newreq->url = NULL;
+       newreq->local = -1;
+       newreq->state = WAITING;
+       snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
+       snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
+                "%s.temp", filename);
+       newreq->slot = NULL;
+       newreq->next = NULL;
+
+       if (object_queue_head == NULL) {
+               object_queue_head = newreq;
+       } else {
+               tail = object_queue_head;
+               while (tail->next != NULL) {
+                       tail = tail->next;
+               }
+               tail->next = newreq;
+       }
+
+#ifdef USE_CURL_MULTI
+       fill_active_slots();
+       step_active_slots();
+#endif
+}
+
+static int fetch_index(struct alt_base *repo, unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename;
+       char *url;
+       char tmpfile[PATH_MAX];
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       FILE *indexfile;
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       if (has_pack_index(sha1))
+               return 0;
+
+       if (get_verbosely)
+               fprintf(stderr, "Getting index for pack %s\n", hex);
+
+       url = xmalloc(strlen(repo->base) + 64);
+       sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
+
+       filename = sha1_pack_index_name(sha1);
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       indexfile = fopen(tmpfile, "a");
+       if (!indexfile)
+               return error("Unable to open local file %s for pack index",
+                            filename);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = indexfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(indexfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of index for pack %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       fclose(indexfile);
+                       return error("Unable to get pack index %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               fclose(indexfile);
+               return error("Unable to start request");
+       }
+
+       fclose(indexfile);
+
+       return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(struct alt_base *repo, unsigned char *sha1)
+{
+       struct packed_git *new_pack;
+       if (has_pack_file(sha1))
+               return 0; // don't list this as something we can get
+
+       if (fetch_index(repo, sha1))
+               return -1;
+
+       new_pack = parse_pack_index(sha1);
+       new_pack->next = repo->packs;
+       repo->packs = new_pack;
+       return 0;
+}
+
+static void process_alternates_response(void *callback_data)
+{
+       struct alternates_request *alt_req =
+               (struct alternates_request *)callback_data;
+       struct active_request_slot *slot = alt_req->slot;
+       struct alt_base *tail = alt;
+       char *base = alt_req->base;
+       static const char null_byte = '\0';
+       char *data;
+       int i = 0;
+
+       if (alt_req->http_specific) {
+               if (slot->curl_result != CURLE_OK ||
+                   !alt_req->buffer->posn) {
+
+                       /* Try reusing the slot to get non-http alternates */
+                       alt_req->http_specific = 0;
+                       sprintf(alt_req->url, "%s/objects/info/alternates",
+                               base);
+                       curl_easy_setopt(slot->curl, CURLOPT_URL,
+                                        alt_req->url);
+                       active_requests++;
+                       slot->in_use = 1;
+                       if (slot->finished != NULL)
+                               (*slot->finished) = 0;
+                       if (!start_active_slot(slot)) {
+                               got_alternates = -1;
+                               slot->in_use = 0;
+                               if (slot->finished != NULL)
+                                       (*slot->finished) = 1;
+                       }
+                       return;
+               }
+       } else if (slot->curl_result != CURLE_OK) {
+               if (slot->http_code != 404 &&
+                   slot->curl_result != CURLE_FILE_COULDNT_READ_FILE) {
+                       got_alternates = -1;
+                       return;
+               }
+       }
+
+       fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
+       alt_req->buffer->posn--;
+       data = alt_req->buffer->buffer;
+
+       while (i < alt_req->buffer->posn) {
+               int posn = i;
+               while (posn < alt_req->buffer->posn && data[posn] != '\n')
+                       posn++;
+               if (data[posn] == '\n') {
+                       int okay = 0;
+                       int serverlen = 0;
+                       struct alt_base *newalt;
+                       char *target = NULL;
+                       char *path;
+                       if (data[i] == '/') {
+                               serverlen = strchr(base + 8, '/') - base;
+                               okay = 1;
+                       } else if (!memcmp(data + i, "../", 3)) {
+                               i += 3;
+                               serverlen = strlen(base);
+                               while (i + 2 < posn &&
+                                      !memcmp(data + i, "../", 3)) {
+                                       do {
+                                               serverlen--;
+                                       } while (serverlen &&
+                                                base[serverlen - 1] != '/');
+                                       i += 3;
+                               }
+                               // If the server got removed, give up.
+                               okay = strchr(base, ':') - base + 3 <
+                                       serverlen;
+                       } else if (alt_req->http_specific) {
+                               char *colon = strchr(data + i, ':');
+                               char *slash = strchr(data + i, '/');
+                               if (colon && slash && colon < data + posn &&
+                                   slash < data + posn && colon < slash) {
+                                       okay = 1;
+                               }
+                       }
+                       // skip 'objects' at end
+                       if (okay) {
+                               target = xmalloc(serverlen + posn - i - 6);
+                               strncpy(target, base, serverlen);
+                               strncpy(target + serverlen, data + i,
+                                       posn - i - 7);
+                               target[serverlen + posn - i - 7] = '\0';
+                               if (get_verbosely)
+                                       fprintf(stderr,
+                                               "Also look at %s\n", target);
+                               newalt = xmalloc(sizeof(*newalt));
+                               newalt->next = NULL;
+                               newalt->base = target;
+                               newalt->got_indices = 0;
+                               newalt->packs = NULL;
+                               path = strstr(target, "//");
+                               if (path) {
+                                       path = strchr(path+2, '/');
+                                       if (path)
+                                               newalt->path_len = strlen(path);
+                               }
+
+                               while (tail->next != NULL)
+                                       tail = tail->next;
+                               tail->next = newalt;
+                       }
+               }
+               i = posn + 1;
+       }
+
+       got_alternates = 1;
+}
+
+static void fetch_alternates(char *base)
+{
+       struct buffer buffer;
+       char *url;
+       char *data;
+       struct active_request_slot *slot;
+       struct alternates_request alt_req;
+
+       /* If another request has already started fetching alternates,
+          wait for them to arrive and return to processing this request's
+          curl message */
+#ifdef USE_CURL_MULTI
+       while (got_alternates == 0) {
+               step_active_slots();
+       }
+#endif
+
+       /* Nothing to do if they've already been fetched */
+       if (got_alternates == 1)
+               return;
+
+       /* Start the fetch */
+       got_alternates = 0;
+
+       data = xmalloc(4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = data;
+
+       if (get_verbosely)
+               fprintf(stderr, "Getting alternates list for %s\n", base);
+
+       url = xmalloc(strlen(base) + 31);
+       sprintf(url, "%s/objects/info/http-alternates", base);
+
+       /* Use a callback to process the result, since another request
+          may fail and need to have alternates loaded before continuing */
+       slot = get_active_slot();
+       slot->callback_func = process_alternates_response;
+       slot->callback_data = &alt_req;
+
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+
+       alt_req.base = base;
+       alt_req.url = url;
+       alt_req.buffer = &buffer;
+       alt_req.http_specific = 1;
+       alt_req.slot = slot;
+
+       if (start_active_slot(slot))
+               run_active_slot(slot);
+       else
+               got_alternates = -1;
+
+       free(data);
+       free(url);
+}
+
+#ifndef NO_EXPAT
+static void
+xml_start_tag(void *userData, const char *name, const char **atts)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = strchr(name, ':');
+       int new_len;
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       new_len = strlen(ctx->name) + strlen(c) + 2;
+
+       if (new_len > ctx->len) {
+               ctx->name = xrealloc(ctx->name, new_len);
+               ctx->len = new_len;
+       }
+       strcat(ctx->name, ".");
+       strcat(ctx->name, c);
+
+       if (ctx->cdata) {
+               free(ctx->cdata);
+               ctx->cdata = NULL;
+       }
+
+       ctx->userFunc(ctx, 0);
+}
+
+static void
+xml_end_tag(void *userData, const char *name)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = strchr(name, ':');
+       char *ep;
+
+       ctx->userFunc(ctx, 1);
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
+       *ep = 0;
+}
+
+static void
+xml_cdata(void *userData, const XML_Char *s, int len)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       if (ctx->cdata)
+               free(ctx->cdata);
+       ctx->cdata = xcalloc(len+1, 1);
+       strncpy(ctx->cdata, s, len);
+}
+
+static int remote_ls(struct alt_base *repo, const char *path, int flags,
+                    void (*userFunc)(struct remote_ls_ctx *ls),
+                    void *userData);
+
+static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
+                       if (ls->dentry_flags & IS_DIR) {
+                               if (ls->flags & PROCESS_DIRS) {
+                                       ls->userFunc(ls);
+                               }
+                               if (strcmp(ls->dentry_name, ls->path) &&
+                                   ls->flags & RECURSIVE) {
+                                       ls->rc = remote_ls(ls->repo,
+                                                          ls->dentry_name,
+                                                          ls->flags,
+                                                          ls->userFunc,
+                                                          ls->userData);
+                               }
+                       } else if (ls->flags & PROCESS_FILES) {
+                               ls->userFunc(ls);
+                       }
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
+                       ls->dentry_name = xmalloc(strlen(ctx->cdata) -
+                                                 ls->repo->path_len + 1);
+                       strcpy(ls->dentry_name, ctx->cdata + ls->repo->path_len);
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
+                       ls->dentry_flags |= IS_DIR;
+               }
+       } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
+               if (ls->dentry_name) {
+                       free(ls->dentry_name);
+               }
+               ls->dentry_name = NULL;
+               ls->dentry_flags = 0;
+       }
+}
+
+static int remote_ls(struct alt_base *repo, const char *path, int flags,
+                    void (*userFunc)(struct remote_ls_ctx *ls),
+                    void *userData)
+{
+       char *url = xmalloc(strlen(repo->base) + strlen(path) + 1);
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       struct remote_ls_ctx ls;
+
+       ls.flags = flags;
+       ls.repo = repo;
+       ls.path = strdup(path);
+       ls.dentry_name = NULL;
+       ls.dentry_flags = 0;
+       ls.userData = userData;
+       ls.userFunc = userFunc;
+       ls.rc = 0;
+
+       sprintf(url, "%s%s", repo->base, path);
+
+       out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 1");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_remote_ls_ctx;
+                       ctx.userData = &ls;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               ls.rc = error("XML error: %s",
+                                             XML_ErrorString(
+                                                     XML_GetErrorCode(parser)));
+                       }
+               } else {
+                       ls.rc = -1;
+               }
+       } else {
+               ls.rc = error("Unable to start PROPFIND request");
+       }
+
+       free(ls.path);
+       free(url);
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
+
+       return ls.rc;
+}
+
+static void process_ls_pack(struct remote_ls_ctx *ls)
+{
+       unsigned char sha1[20];
+
+       if (strlen(ls->dentry_name) == 63 &&
+           !strncmp(ls->dentry_name, "objects/pack/pack-", 18) &&
+           !strncmp(ls->dentry_name+58, ".pack", 5)) {
+               get_sha1_hex(ls->dentry_name + 18, sha1);
+               setup_index(ls->repo, sha1);
+       }
+}
+#endif
+
+static int fetch_indices(struct alt_base *repo)
+{
+       unsigned char sha1[20];
+       char *url;
+       struct buffer buffer;
+       char *data;
+       int i = 0;
+
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       if (repo->got_indices)
+               return 0;
+
+       data = xmalloc(4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = data;
+
+       if (get_verbosely)
+               fprintf(stderr, "Getting pack list for %s\n", repo->base);
+
+#ifndef NO_EXPAT
+       if (remote_ls(repo, "objects/pack/", PROCESS_FILES,
+                     process_ls_pack, NULL) == 0)
+               return 0;
+#endif
+
+       url = xmalloc(strlen(repo->base) + 21);
+       sprintf(url, "%s/objects/info/packs", repo->base);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       if (results.http_code == 404 ||
+                           results.curl_result == CURLE_FILE_COULDNT_READ_FILE) {
+                               repo->got_indices = 1;
+                               free(buffer.buffer);
+                               return 0;
+                       } else {
+                               repo->got_indices = 0;
+                               free(buffer.buffer);
+                               return error("%s", curl_errorstr);
+                       }
+               }
+       } else {
+               repo->got_indices = 0;
+               free(buffer.buffer);
+               return error("Unable to start request");
+       }
+
+       data = buffer.buffer;
+       while (i < buffer.posn) {
+               switch (data[i]) {
+               case 'P':
+                       i++;
+                       if (i + 52 <= buffer.posn &&
+                           !strncmp(data + i, " pack-", 6) &&
+                           !strncmp(data + i + 46, ".pack\n", 6)) {
+                               get_sha1_hex(data + i + 6, sha1);
+                               setup_index(repo, sha1);
+                               i += 51;
+                               break;
+                       }
+               default:
+                       while (i < buffer.posn && data[i] != '\n')
+                               i++;
+               }
+               i++;
+       }
+
+       free(buffer.buffer);
+       repo->got_indices = 1;
+       return 0;
+}
+
+static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
+{
+       char *url;
+       struct packed_git *target;
+       struct packed_git **lst;
+       FILE *packfile;
+       char *filename;
+       char tmpfile[PATH_MAX];
+       int ret;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       if (fetch_indices(repo))
+               return -1;
+       target = find_sha1_pack(sha1, repo->packs);
+       if (!target)
+               return -1;
+
+       if (get_verbosely) {
+               fprintf(stderr, "Getting pack %s\n",
+                       sha1_to_hex(target->sha1));
+               fprintf(stderr, " which contains %s\n",
+                       sha1_to_hex(sha1));
+       }
+
+       url = xmalloc(strlen(repo->base) + 65);
+       sprintf(url, "%s/objects/pack/pack-%s.pack",
+               repo->base, sha1_to_hex(target->sha1));
+
+       filename = sha1_pack_name(target->sha1);
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       packfile = fopen(tmpfile, "a");
+       if (!packfile)
+               return error("Unable to open local file %s for pack",
+                            filename);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = packfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(packfile);
+       if (prev_posn>0) {
+               if (get_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       fclose(packfile);
+                       return error("Unable to get pack file %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               fclose(packfile);
+               return error("Unable to start request");
+       }
+
+       fclose(packfile);
+
+       ret = move_temp_to_file(tmpfile, filename);
+       if (ret)
+               return ret;
+
+       lst = &repo->packs;
+       while (*lst != target)
+               lst = &((*lst)->next);
+       *lst = (*lst)->next;
+
+       if (verify_pack(target, 0))
+               return -1;
+       install_packed_git(target);
+
+       return 0;
+}
+
+static void abort_object_request(struct object_request *obj_req)
+{
+       if (obj_req->local >= 0) {
+               close(obj_req->local);
+               obj_req->local = -1;
+       }
+       unlink(obj_req->tmpfile);
+       if (obj_req->slot) {
+               release_active_slot(obj_req->slot);
+               obj_req->slot = NULL;
+       }
+       release_object_request(obj_req);
+}
+
+static int fetch_object(struct alt_base *repo, unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       int ret = 0;
+       struct object_request *obj_req = object_queue_head;
+
+       while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20))
+               obj_req = obj_req->next;
+       if (obj_req == NULL)
+               return error("Couldn't find request for %s in the queue", hex);
+
+       if (has_sha1_file(obj_req->sha1)) {
+               abort_object_request(obj_req);
+               return 0;
+       }
+
+#ifdef USE_CURL_MULTI
+       while (obj_req->state == WAITING) {
+               step_active_slots();
+       }
+#else
+       start_object_request(obj_req);
+#endif
+
+       while (obj_req->state == ACTIVE) {
+               run_active_slot(obj_req->slot);
+       }
+       if (obj_req->local != -1) {
+               close(obj_req->local); obj_req->local = -1;
+       }
+
+       if (obj_req->state == ABORTED) {
+               ret = error("Request for %s aborted", hex);
+       } else if (obj_req->curl_result != CURLE_OK &&
+                  obj_req->http_code != 416) {
+               if (obj_req->http_code == 404 ||
+                   obj_req->curl_result == CURLE_FILE_COULDNT_READ_FILE)
+                       ret = -1; /* Be silent, it is probably in a pack. */
+               else
+                       ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
+                                   obj_req->errorstr, obj_req->curl_result,
+                                   obj_req->http_code, hex);
+       } else if (obj_req->zret != Z_STREAM_END) {
+               corrupt_object_found++;
+               ret = error("File %s (%s) corrupt", hex, obj_req->url);
+       } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) {
+               ret = error("File %s has bad hash", hex);
+       } else if (obj_req->rename < 0) {
+               ret = error("unable to write sha1 filename %s",
+                           obj_req->filename);
+       }
+
+       release_object_request(obj_req);
+       return ret;
+}
+
+int fetch(unsigned char *sha1)
+{
+       struct alt_base *altbase = alt;
+
+       if (!fetch_object(altbase, sha1))
+               return 0;
+       while (altbase) {
+               if (!fetch_pack(altbase, sha1))
+                       return 0;
+               fetch_alternates(alt->base);
+               altbase = altbase->next;
+       }
+       return error("Unable to find %s under %s", sha1_to_hex(sha1),
+                    alt->base);
+}
+
+static inline int needs_quote(int ch)
+{
+       switch (ch) {
+       case '/': case '-': case '.':
+       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static inline int hex(int v)
+{
+       if (v < 10) return '0' + v;
+       else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+       const char *cp;
+       char *dp, *qref;
+       int len, baselen, ch;
+
+       baselen = strlen(base);
+       len = baselen + 6; /* "refs/" + NUL */
+       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+               if (needs_quote(ch))
+                       len += 2; /* extra two hex plus replacement % */
+       qref = xmalloc(len);
+       memcpy(qref, base, baselen);
+       memcpy(qref + baselen, "refs/", 5);
+       for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+               if (needs_quote(ch)) {
+                       *dp++ = '%';
+                       *dp++ = hex((ch >> 4) & 0xF);
+                       *dp++ = hex(ch & 0xF);
+               }
+               else
+                       *dp++ = ch;
+       }
+       *dp = 0;
+
+       return qref;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+        char *url;
+        char hex[42];
+        struct buffer buffer;
+       char *base = alt->base;
+       struct active_request_slot *slot;
+       struct slot_results results;
+        buffer.size = 41;
+        buffer.posn = 0;
+        buffer.buffer = hex;
+        hex[41] = '\0';
+
+       url = quote_ref_url(base, ref);
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK)
+                       return error("Couldn't get %s for %s\n%s",
+                                    url, ref, curl_errorstr);
+       } else {
+               return error("Unable to start request");
+       }
+
+        hex[40] = '\0';
+        get_sha1_hex(hex, sha1);
+        return 0;
+}
+
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       char *url;
+       char *path;
+       int arg = 1;
+       int rc = 0;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't') {
+                       get_tree = 1;
+               } else if (argv[arg][1] == 'c') {
+                       get_history = 1;
+               } else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               } else if (argv[arg][1] == 'v') {
+                       get_verbosely = 1;
+               } else if (argv[arg][1] == 'w') {
+                       write_ref = argv[arg + 1];
+                       arg++;
+               } else if (!strcmp(argv[arg], "--recover")) {
+                       get_recover = 1;
+               }
+               arg++;
+       }
+       if (argc < arg + 2) {
+               usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+       write_ref_log_details = url;
+
+       http_init();
+
+       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+
+       alt = xmalloc(sizeof(*alt));
+       alt->base = url;
+       alt->got_indices = 0;
+       alt->packs = NULL;
+       alt->next = NULL;
+       path = strstr(url, "//");
+       if (path) {
+               path = strchr(path+2, '/');
+               if (path)
+                       alt->path_len = strlen(path);
+       }
+
+       if (pull(commit_id))
+               rc = 1;
+
+       http_cleanup();
+
+       curl_slist_free_all(no_pragma_header);
+
+       if (corrupt_object_found) {
+               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");
+       }
+       return rc;
+}
diff --git a/http-push.c b/http-push.c
new file mode 100644 (file)
index 0000000..b39b36b
--- /dev/null
@@ -0,0 +1,2580 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "fetch.h"
+#include "tag.h"
+#include "blob.h"
+#include "http.h"
+#include "refs.h"
+#include "diff.h"
+#include "revision.h"
+#include "exec_cmd.h"
+
+#include <expat.h>
+
+static const char http_push_usage[] =
+"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n";
+
+#ifndef XML_STATUS_OK
+enum XML_Status {
+  XML_STATUS_OK = 1,
+  XML_STATUS_ERROR = 0
+};
+#define XML_STATUS_OK    1
+#define XML_STATUS_ERROR 0
+#endif
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
+/* DAV methods */
+#define DAV_LOCK "LOCK"
+#define DAV_MKCOL "MKCOL"
+#define DAV_MOVE "MOVE"
+#define DAV_PROPFIND "PROPFIND"
+#define DAV_PUT "PUT"
+#define DAV_UNLOCK "UNLOCK"
+#define DAV_DELETE "DELETE"
+
+/* DAV lock flags */
+#define DAV_PROP_LOCKWR (1u << 0)
+#define DAV_PROP_LOCKEX (1u << 1)
+#define DAV_LOCK_OK (1u << 2)
+
+/* DAV XML properties */
+#define DAV_CTX_LOCKENTRY ".multistatus.response.propstat.prop.supportedlock.lockentry"
+#define DAV_CTX_LOCKTYPE_WRITE ".multistatus.response.propstat.prop.supportedlock.lockentry.locktype.write"
+#define DAV_CTX_LOCKTYPE_EXCLUSIVE ".multistatus.response.propstat.prop.supportedlock.lockentry.lockscope.exclusive"
+#define DAV_ACTIVELOCK_OWNER ".prop.lockdiscovery.activelock.owner.href"
+#define DAV_ACTIVELOCK_TIMEOUT ".prop.lockdiscovery.activelock.timeout"
+#define DAV_ACTIVELOCK_TOKEN ".prop.lockdiscovery.activelock.locktoken.href"
+#define DAV_PROPFIND_RESP ".multistatus.response"
+#define DAV_PROPFIND_NAME ".multistatus.response.href"
+#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
+
+/* DAV request body templates */
+#define PROPFIND_SUPPORTEDLOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
+#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
+#define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>"
+
+#define LOCK_TIME 600
+#define LOCK_REFRESH 30
+
+/* bits #0-15 in revision.h */
+
+#define LOCAL    (1u<<16)
+#define REMOTE   (1u<<17)
+#define FETCHING (1u<<18)
+#define PUSHING  (1u<<19)
+
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+static int pushing = 0;
+static int aborted = 0;
+static signed char remote_dir_exists[256];
+
+static struct curl_slist *no_pragma_header;
+static struct curl_slist *default_headers;
+
+static int push_verbosely = 0;
+static int push_all = 0;
+static int force_all = 0;
+
+static struct object_list *objects = NULL;
+
+struct repo
+{
+       char *url;
+       int path_len;
+       int has_info_refs;
+       int can_update_info_refs;
+       int has_info_packs;
+       struct packed_git *packs;
+       struct remote_lock *locks;
+};
+
+static struct repo *remote = NULL;
+
+enum transfer_state {
+       NEED_FETCH,
+       RUN_FETCH_LOOSE,
+       RUN_FETCH_PACKED,
+       NEED_PUSH,
+       RUN_MKCOL,
+       RUN_PUT,
+       RUN_MOVE,
+       ABORTED,
+       COMPLETE,
+};
+
+struct transfer_request
+{
+       struct object *obj;
+       char *url;
+       char *dest;
+       struct remote_lock *lock;
+       struct curl_slist *headers;
+       struct buffer buffer;
+       char filename[PATH_MAX];
+       char tmpfile[PATH_MAX];
+       int local_fileno;
+       FILE *local_stream;
+       enum transfer_state state;
+       CURLcode curl_result;
+       char errorstr[CURL_ERROR_SIZE];
+       long http_code;
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+       z_stream stream;
+       int zret;
+       int rename;
+       void *userData;
+       struct active_request_slot *slot;
+       struct transfer_request *next;
+};
+
+static struct transfer_request *request_queue_head = NULL;
+
+struct xml_ctx
+{
+       char *name;
+       int len;
+       char *cdata;
+       void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
+       void *userData;
+};
+
+struct remote_lock
+{
+       char *url;
+       char *owner;
+       char *token;
+       time_t start_time;
+       long timeout;
+       int refreshing;
+       struct remote_lock *next;
+};
+
+/* Flags that control remote_ls processing */
+#define PROCESS_FILES (1u << 0)
+#define PROCESS_DIRS  (1u << 1)
+#define RECURSIVE     (1u << 2)
+
+/* Flags that remote_ls passes to callback functions */
+#define IS_DIR (1u << 0)
+
+struct remote_ls_ctx
+{
+       char *path;
+       void (*userFunc)(struct remote_ls_ctx *ls);
+       void *userData;
+       int flags;
+       char *dentry_name;
+       int dentry_flags;
+       struct remote_ls_ctx *parent;
+};
+
+static void finish_request(struct transfer_request *request);
+static void release_request(struct transfer_request *request);
+
+static void process_response(void *callback_data)
+{
+       struct transfer_request *request =
+               (struct transfer_request *)callback_data;
+
+       finish_request(request);
+}
+
+#ifdef USE_CURL_MULTI
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+                              void *data)
+{
+       unsigned char expn[4096];
+       size_t size = eltsize * nmemb;
+       int posn = 0;
+       struct transfer_request *request = (struct transfer_request *)data;
+       do {
+               ssize_t retval = write(request->local_fileno,
+                                      ptr + posn, size - posn);
+               if (retval < 0)
+                       return posn;
+               posn += retval;
+       } while (posn < size);
+
+       request->stream.avail_in = size;
+       request->stream.next_in = ptr;
+       do {
+               request->stream.next_out = expn;
+               request->stream.avail_out = sizeof(expn);
+               request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+               SHA1_Update(&request->c, expn,
+                           sizeof(expn) - request->stream.avail_out);
+       } while (request->stream.avail_in && request->zret == Z_OK);
+       data_received++;
+       return size;
+}
+
+static void start_fetch_loose(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->obj->sha1);
+       char *filename;
+       char prevfile[PATH_MAX];
+       char *url;
+       char *posn;
+       int prevlocal;
+       unsigned char prev_buf[PREV_BUF_SIZE];
+       ssize_t prev_read = 0;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+       struct active_request_slot *slot;
+
+       filename = sha1_file_name(request->obj->sha1);
+       snprintf(request->filename, sizeof(request->filename), "%s", filename);
+       snprintf(request->tmpfile, sizeof(request->tmpfile),
+                "%s.temp", filename);
+
+       snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+       unlink(prevfile);
+       rename(request->tmpfile, prevfile);
+       unlink(request->tmpfile);
+
+       if (request->local_fileno != -1)
+               error("fd leakage in start: %d", request->local_fileno);
+       request->local_fileno = open(request->tmpfile,
+                                    O_WRONLY | O_CREAT | O_EXCL, 0666);
+       /* This could have failed due to the "lazy directory creation";
+        * try to mkdir the last path component.
+        */
+       if (request->local_fileno < 0 && errno == ENOENT) {
+               char *dir = strrchr(request->tmpfile, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(request->tmpfile, 0777);
+                       *dir = '/';
+               }
+               request->local_fileno = open(request->tmpfile,
+                                            O_WRONLY | O_CREAT | O_EXCL, 0666);
+       }
+
+       if (request->local_fileno < 0) {
+               request->state = ABORTED;
+               error("Couldn't create temporary file %s for %s: %s",
+                     request->tmpfile, request->filename, strerror(errno));
+               return;
+       }
+
+       memset(&request->stream, 0, sizeof(request->stream));
+
+       inflateInit(&request->stream);
+
+       SHA1_Init(&request->c);
+
+       url = xmalloc(strlen(remote->url) + 50);
+       request->url = xmalloc(strlen(remote->url) + 50);
+       strcpy(url, remote->url);
+       posn = url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       strcpy(request->url, url);
+
+       /* If a previous temp file is present, process what was already
+          fetched. */
+       prevlocal = open(prevfile, O_RDONLY);
+       if (prevlocal != -1) {
+               do {
+                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       if (prev_read>0) {
+                               if (fwrite_sha1_file(prev_buf,
+                                                    1,
+                                                    prev_read,
+                                                    request) == prev_read) {
+                                       prev_posn += prev_read;
+                               } else {
+                                       prev_read = -1;
+                               }
+                       }
+               } while (prev_read > 0);
+               close(prevlocal);
+       }
+       unlink(prevfile);
+
+       /* Reset inflate/SHA1 if there was an error reading the previous temp
+          file; also rewind to the beginning of the local file. */
+       if (prev_read == -1) {
+               memset(&request->stream, 0, sizeof(request->stream));
+               inflateInit(&request->stream);
+               SHA1_Init(&request->c);
+               if (prev_posn>0) {
+                       prev_posn = 0;
+                       lseek(request->local_fileno, SEEK_SET, 0);
+                       ftruncate(request->local_fileno, 0);
+               }
+       }
+
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       request->slot = slot;
+
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+       /* If we have successfully processed data from a previous fetch
+          attempt, only fetch the data we don't already have. */
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of object %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl,
+                                CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       request->state = RUN_FETCH_LOOSE;
+       if (!start_active_slot(slot)) {
+               fprintf(stderr, "Unable to start GET request\n");
+               remote->can_update_info_refs = 0;
+               release_request(request);
+       }
+}
+
+static void start_mkcol(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->obj->sha1);
+       struct active_request_slot *slot;
+       char *posn;
+
+       request->url = xmalloc(strlen(remote->url) + 13);
+       strcpy(request->url, remote->url);
+       posn = request->url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       strcpy(posn, "/");
+
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_MKCOL;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+               request->url = NULL;
+       }
+}
+#endif
+
+static void start_fetch_packed(struct transfer_request *request)
+{
+       char *url;
+       struct packed_git *target;
+       FILE *packfile;
+       char *filename;
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       struct transfer_request *check_request = request_queue_head;
+       struct active_request_slot *slot;
+
+       target = find_sha1_pack(request->obj->sha1, remote->packs);
+       if (!target) {
+               fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1));
+               remote->can_update_info_refs = 0;
+               release_request(request);
+               return;
+       }
+
+       fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1));
+       fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1));
+
+       filename = sha1_pack_name(target->sha1);
+       snprintf(request->filename, sizeof(request->filename), "%s", filename);
+       snprintf(request->tmpfile, sizeof(request->tmpfile),
+                "%s.temp", filename);
+
+       url = xmalloc(strlen(remote->url) + 64);
+       sprintf(url, "%sobjects/pack/pack-%s.pack",
+               remote->url, sha1_to_hex(target->sha1));
+
+       /* Make sure there isn't another open request for this pack */
+       while (check_request) {
+               if (check_request->state == RUN_FETCH_PACKED &&
+                   !strcmp(check_request->url, url)) {
+                       free(url);
+                       release_request(request);
+                       return;
+               }
+               check_request = check_request->next;
+       }
+
+       packfile = fopen(request->tmpfile, "a");
+       if (!packfile) {
+               fprintf(stderr, "Unable to open local file %s for pack",
+                       filename);
+               remote->can_update_info_refs = 0;
+               free(url);
+               return;
+       }
+
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       request->slot = slot;
+       request->local_stream = packfile;
+       request->userData = target;
+
+       request->url = url;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = packfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(packfile);
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of pack %s at byte %ld\n",
+                               sha1_to_hex(target->sha1), prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       /* Try to get the request started, abort the request on error */
+       request->state = RUN_FETCH_PACKED;
+       if (!start_active_slot(slot)) {
+               fprintf(stderr, "Unable to start GET request\n");
+               remote->can_update_info_refs = 0;
+               release_request(request);
+       }
+}
+
+static void start_put(struct transfer_request *request)
+{
+       char *hex = sha1_to_hex(request->obj->sha1);
+       struct active_request_slot *slot;
+       char *posn;
+       char type[20];
+       char hdr[50];
+       void *unpacked;
+       unsigned long len;
+       int hdrlen;
+       ssize_t size;
+       z_stream stream;
+
+       unpacked = read_sha1_file(request->obj->sha1, type, &len);
+       hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len + hdrlen);
+       request->buffer.buffer = xmalloc(size);
+
+       /* Compress it */
+       stream.next_out = request->buffer.buffer;
+       stream.avail_out = size;
+
+       /* First header.. */
+       stream.next_in = (void *)hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
+
+       /* Then the data itself.. */
+       stream.next_in = unpacked;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       free(unpacked);
+
+       request->buffer.size = stream.total_out;
+       request->buffer.posn = 0;
+
+       request->url = xmalloc(strlen(remote->url) + 
+                              strlen(request->lock->token) + 51);
+       strcpy(request->url, remote->url);
+       posn = request->url + strlen(remote->url);
+       strcpy(posn, "objects/");
+       posn += 8;
+       memcpy(posn, hex, 2);
+       posn += 2;
+       *(posn++) = '/';
+       strcpy(posn, hex + 2);
+       request->dest = xmalloc(strlen(request->url) + 14);
+       sprintf(request->dest, "Destination: %s", request->url);
+       posn += 38;
+       *(posn++) = '.';
+       strcpy(posn, request->lock->token);
+
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_PUT;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+               request->url = NULL;
+       }
+}
+
+static void start_move(struct transfer_request *request)
+{
+       struct active_request_slot *slot;
+       struct curl_slist *dav_headers = NULL;
+
+       slot = get_active_slot();
+       slot->callback_func = process_response;
+       slot->callback_data = request;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
+       dav_headers = curl_slist_append(dav_headers, request->dest);
+       dav_headers = curl_slist_append(dav_headers, "Overwrite: T");
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+       if (start_active_slot(slot)) {
+               request->slot = slot;
+               request->state = RUN_MOVE;
+       } else {
+               request->state = ABORTED;
+               free(request->url);
+               request->url = NULL;
+       }
+}
+
+static int refresh_lock(struct remote_lock *lock)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *if_header;
+       char timeout_header[25];
+       struct curl_slist *dav_headers = NULL;
+       int rc = 0;
+
+       lock->refreshing = 1;
+
+       if_header = xmalloc(strlen(lock->token) + 25);
+       sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+       sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
+       dav_headers = curl_slist_append(dav_headers, if_header);
+       dav_headers = curl_slist_append(dav_headers, timeout_header);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       fprintf(stderr, "LOCK HTTP error %ld\n",
+                               results.http_code);
+               } else {
+                       lock->start_time = time(NULL);
+                       rc = 1;
+               }
+       }
+
+       lock->refreshing = 0;
+       curl_slist_free_all(dav_headers);
+       free(if_header);
+
+       return rc;
+}
+
+static void check_locks(void)
+{
+       struct remote_lock *lock = remote->locks;
+       time_t current_time = time(NULL);
+       int time_remaining;
+
+       while (lock) {
+               time_remaining = lock->start_time + lock->timeout -
+                       current_time;
+               if (!lock->refreshing && time_remaining < LOCK_REFRESH) {
+                       if (!refresh_lock(lock)) {
+                               fprintf(stderr,
+                                       "Unable to refresh lock for %s\n",
+                                       lock->url);
+                               aborted = 1;
+                               return;
+                       }
+               }
+               lock = lock->next;
+       }
+}
+
+static void release_request(struct transfer_request *request)
+{
+       struct transfer_request *entry = request_queue_head;
+
+       if (request == request_queue_head) {
+               request_queue_head = request->next;
+       } else {
+               while (entry->next != NULL && entry->next != request)
+                       entry = entry->next;
+               if (entry->next == request)
+                       entry->next = entry->next->next;
+       }
+
+       if (request->local_fileno != -1)
+               close(request->local_fileno);
+       if (request->local_stream)
+               fclose(request->local_stream);
+       if (request->url != NULL)
+               free(request->url);
+       free(request);
+}
+
+static void finish_request(struct transfer_request *request)
+{
+       struct stat st;
+       struct packed_git *target;
+       struct packed_git **lst;
+
+       request->curl_result = request->slot->curl_result;
+       request->http_code = request->slot->http_code;
+       request->slot = NULL;
+
+       /* Keep locks active */
+       check_locks();
+
+       if (request->headers != NULL)
+               curl_slist_free_all(request->headers);
+
+       /* URL is reused for MOVE after PUT */
+       if (request->state != RUN_PUT) {
+               free(request->url);
+               request->url = NULL;
+       }
+
+       if (request->state == RUN_MKCOL) {
+               if (request->curl_result == CURLE_OK ||
+                   request->http_code == 405) {
+                       remote_dir_exists[request->obj->sha1[0]] = 1;
+                       start_put(request);
+               } else {
+                       fprintf(stderr, "MKCOL %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->obj->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_PUT) {
+               if (request->curl_result == CURLE_OK) {
+                       start_move(request);
+               } else {
+                       fprintf(stderr, "PUT %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->obj->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_MOVE) {
+               if (request->curl_result == CURLE_OK) {
+                       if (push_verbosely)
+                               fprintf(stderr, "    sent %s\n",
+                                       sha1_to_hex(request->obj->sha1));
+                       request->obj->flags |= REMOTE;
+                       release_request(request);
+               } else {
+                       fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n",
+                               sha1_to_hex(request->obj->sha1),
+                               request->curl_result, request->http_code);
+                       request->state = ABORTED;
+                       aborted = 1;
+               }
+       } else if (request->state == RUN_FETCH_LOOSE) {
+               fchmod(request->local_fileno, 0444);
+               close(request->local_fileno); request->local_fileno = -1;
+
+               if (request->curl_result != CURLE_OK &&
+                   request->http_code != 416) {
+                       if (stat(request->tmpfile, &st) == 0) {
+                               if (st.st_size == 0)
+                                       unlink(request->tmpfile);
+                       }
+               } else {
+                       if (request->http_code == 416)
+                               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+
+                       inflateEnd(&request->stream);
+                       SHA1_Final(request->real_sha1, &request->c);
+                       if (request->zret != Z_STREAM_END) {
+                               unlink(request->tmpfile);
+                       } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) {
+                               unlink(request->tmpfile);
+                       } else {
+                               request->rename =
+                                       move_temp_to_file(
+                                               request->tmpfile,
+                                               request->filename);
+                               if (request->rename == 0) {
+                                       request->obj->flags |= (LOCAL | REMOTE);
+                               }
+                       }
+               }
+
+               /* Try fetching packed if necessary */
+               if (request->obj->flags & LOCAL)
+                       release_request(request);
+               else
+                       start_fetch_packed(request);
+
+       } else if (request->state == RUN_FETCH_PACKED) {
+               if (request->curl_result != CURLE_OK) {
+                       fprintf(stderr, "Unable to get pack file %s\n%s",
+                               request->url, curl_errorstr);
+                       remote->can_update_info_refs = 0;
+               } else {
+                       fclose(request->local_stream);
+                       request->local_stream = NULL;
+                       if (!move_temp_to_file(request->tmpfile,
+                                              request->filename)) {
+                               target = (struct packed_git *)request->userData;
+                               lst = &remote->packs;
+                               while (*lst != target)
+                                       lst = &((*lst)->next);
+                               *lst = (*lst)->next;
+
+                               if (!verify_pack(target, 0))
+                                       install_packed_git(target);
+                               else
+                                       remote->can_update_info_refs = 0;
+                       }
+               }
+               release_request(request);
+       }
+}
+
+#ifdef USE_CURL_MULTI
+void fill_active_slots(void)
+{
+       struct transfer_request *request = request_queue_head;
+       struct transfer_request *next;
+       struct active_request_slot *slot = active_queue_head;
+       int num_transfers;
+
+       if (aborted)
+               return;
+
+       while (active_requests < max_requests && request != NULL) {
+               next = request->next;
+               if (request->state == NEED_FETCH) {
+                       start_fetch_loose(request);
+               } else if (pushing && request->state == NEED_PUSH) {
+                       if (remote_dir_exists[request->obj->sha1[0]] == 1) {
+                               start_put(request);
+                       } else {
+                               start_mkcol(request);
+                       }
+                       curl_multi_perform(curlm, &num_transfers);
+               }
+               request = next;
+       }
+
+       while (slot != NULL) {
+               if (!slot->in_use && slot->curl != NULL) {
+                       curl_easy_cleanup(slot->curl);
+                       slot->curl = NULL;
+               }
+               slot = slot->next;
+       }
+}
+#endif
+
+static void get_remote_object_list(unsigned char parent);
+
+static void add_fetch_request(struct object *obj)
+{
+       struct transfer_request *request;
+
+       check_locks();
+
+       /*
+        * Don't fetch the object if it's known to exist locally
+        * or is already in the request queue
+        */
+       if (remote_dir_exists[obj->sha1[0]] == -1)
+               get_remote_object_list(obj->sha1[0]);
+       if (obj->flags & (LOCAL | FETCHING))
+               return;
+
+       obj->flags |= FETCHING;
+       request = xmalloc(sizeof(*request));
+       request->obj = obj;
+       request->url = NULL;
+       request->lock = NULL;
+       request->headers = NULL;
+       request->local_fileno = -1;
+       request->local_stream = NULL;
+       request->state = NEED_FETCH;
+       request->next = request_queue_head;
+       request_queue_head = request;
+
+#ifdef USE_CURL_MULTI
+       fill_active_slots();
+       step_active_slots();
+#endif
+}
+
+static int add_send_request(struct object *obj, struct remote_lock *lock)
+{
+       struct transfer_request *request = request_queue_head;
+       struct packed_git *target;
+
+       /* Keep locks active */
+       check_locks();
+
+       /*
+        * Don't push the object if it's known to exist on the remote
+        * or is already in the request queue
+        */
+       if (remote_dir_exists[obj->sha1[0]] == -1)
+               get_remote_object_list(obj->sha1[0]);
+       if (obj->flags & (REMOTE | PUSHING))
+               return 0;
+       target = find_sha1_pack(obj->sha1, remote->packs);
+       if (target) {
+               obj->flags |= REMOTE;
+               return 0;
+       }
+
+       obj->flags |= PUSHING;
+       request = xmalloc(sizeof(*request));
+       request->obj = obj;
+       request->url = NULL;
+       request->lock = lock;
+       request->headers = NULL;
+       request->local_fileno = -1;
+       request->local_stream = NULL;
+       request->state = NEED_PUSH;
+       request->next = request_queue_head;
+       request_queue_head = request;
+
+#ifdef USE_CURL_MULTI
+       fill_active_slots();
+       step_active_slots();
+#endif
+
+       return 1;
+}
+
+static int fetch_index(unsigned char *sha1)
+{
+       char *hex = sha1_to_hex(sha1);
+       char *filename;
+       char *url;
+       char tmpfile[PATH_MAX];
+       long prev_posn = 0;
+       char range[RANGE_HEADER_SIZE];
+       struct curl_slist *range_header = NULL;
+
+       FILE *indexfile;
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       /* Don't use the index if the pack isn't there */
+       url = xmalloc(strlen(remote->url) + 64);
+       sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex);
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       free(url);
+                       return error("Unable to verify pack %s is available",
+                                    hex);
+               }
+       } else {
+               return error("Unable to start request");
+       }
+
+       if (has_pack_index(sha1))
+               return 0;
+
+       if (push_verbosely)
+               fprintf(stderr, "Getting index for pack %s\n", hex);
+
+       sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex);
+
+       filename = sha1_pack_index_name(sha1);
+       snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+       indexfile = fopen(tmpfile, "a");
+       if (!indexfile)
+               return error("Unable to open local file %s for pack index",
+                            filename);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+       slot->local = indexfile;
+
+       /* If there is data present from a previous transfer attempt,
+          resume where it left off */
+       prev_posn = ftell(indexfile);
+       if (prev_posn>0) {
+               if (push_verbosely)
+                       fprintf(stderr,
+                               "Resuming fetch of index for pack %s at byte %ld\n",
+                               hex, prev_posn);
+               sprintf(range, "Range: bytes=%ld-", prev_posn);
+               range_header = curl_slist_append(range_header, range);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+       }
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       free(url);
+                       fclose(indexfile);
+                       return error("Unable to get pack index %s\n%s", url,
+                                    curl_errorstr);
+               }
+       } else {
+               free(url);
+               fclose(indexfile);
+               return error("Unable to start request");
+       }
+
+       free(url);
+       fclose(indexfile);
+
+       return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(unsigned char *sha1)
+{
+       struct packed_git *new_pack;
+
+       if (fetch_index(sha1))
+               return -1;
+
+       new_pack = parse_pack_index(sha1);
+       new_pack->next = remote->packs;
+       remote->packs = new_pack;
+       return 0;
+}
+
+static int fetch_indices(void)
+{
+       unsigned char sha1[20];
+       char *url;
+       struct buffer buffer;
+       char *data;
+       int i = 0;
+
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       data = xcalloc(1, 4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = data;
+
+       if (push_verbosely)
+               fprintf(stderr, "Getting pack list\n");
+
+       url = xmalloc(strlen(remote->url) + 20);
+       sprintf(url, "%sobjects/info/packs", remote->url);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       free(buffer.buffer);
+                       free(url);
+                       if (results.http_code == 404)
+                               return 0;
+                       else
+                               return error("%s", curl_errorstr);
+               }
+       } else {
+               free(buffer.buffer);
+               free(url);
+               return error("Unable to start request");
+       }
+       free(url);
+
+       data = buffer.buffer;
+       while (i < buffer.posn) {
+               switch (data[i]) {
+               case 'P':
+                       i++;
+                       if (i + 52 < buffer.posn &&
+                           !strncmp(data + i, " pack-", 6) &&
+                           !strncmp(data + i + 46, ".pack\n", 6)) {
+                               get_sha1_hex(data + i + 6, sha1);
+                               setup_index(sha1);
+                               i += 51;
+                               break;
+                       }
+               default:
+                       while (data[i] != '\n')
+                               i++;
+               }
+               i++;
+       }
+
+       free(buffer.buffer);
+       return 0;
+}
+
+static inline int needs_quote(int ch)
+{
+       switch (ch) {
+       case '/': case '-': case '.':
+       case 'A'...'Z': case 'a'...'z': case '0'...'9':
+               return 0;
+       default:
+               return 1;
+       }
+}
+
+static inline int hex(int v)
+{
+       if (v < 10) return '0' + v;
+       else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+       const char *cp;
+       char *dp, *qref;
+       int len, baselen, ch;
+
+       baselen = strlen(base);
+       len = baselen + 1;
+       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+               if (needs_quote(ch))
+                       len += 2; /* extra two hex plus replacement % */
+       qref = xmalloc(len);
+       memcpy(qref, base, baselen);
+       for (cp = ref, dp = qref + baselen; (ch = *cp) != 0; cp++) {
+               if (needs_quote(ch)) {
+                       *dp++ = '%';
+                       *dp++ = hex((ch >> 4) & 0xF);
+                       *dp++ = hex(ch & 0xF);
+               }
+               else
+                       *dp++ = ch;
+       }
+       *dp = 0;
+
+       return qref;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+        char *url;
+        char hex[42];
+        struct buffer buffer;
+       char *base = remote->url;
+       struct active_request_slot *slot;
+       struct slot_results results;
+        buffer.size = 41;
+        buffer.posn = 0;
+        buffer.buffer = hex;
+        hex[41] = '\0';
+
+       url = quote_ref_url(base, ref);
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK)
+                       return error("Couldn't get %s for %s\n%s",
+                                    url, ref, curl_errorstr);
+       } else {
+               return error("Unable to start request");
+       }
+
+        hex[40] = '\0';
+        get_sha1_hex(hex, sha1);
+        return 0;
+}
+
+static void one_remote_object(const char *hex)
+{
+       unsigned char sha1[20];
+       struct object *obj;
+
+       if (get_sha1_hex(hex, sha1) != 0)
+               return;
+
+       obj = lookup_object(sha1);
+       if (!obj)
+               obj = parse_object(sha1);
+
+       /* Ignore remote objects that don't exist locally */
+       if (!obj)
+               return;
+
+       obj->flags |= REMOTE;
+       if (!object_list_contains(objects, obj))
+               add_object(obj, &objects, NULL, "");
+}
+
+static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       int *lock_flags = (int *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_CTX_LOCKENTRY)) {
+                       if ((*lock_flags & DAV_PROP_LOCKEX) &&
+                           (*lock_flags & DAV_PROP_LOCKWR)) {
+                               *lock_flags |= DAV_LOCK_OK;
+                       }
+                       *lock_flags &= DAV_LOCK_OK;
+               } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_WRITE)) {
+                       *lock_flags |= DAV_PROP_LOCKWR;
+               } else if (!strcmp(ctx->name, DAV_CTX_LOCKTYPE_EXCLUSIVE)) {
+                       *lock_flags |= DAV_PROP_LOCKEX;
+               }
+       }
+}
+
+static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       struct remote_lock *lock = (struct remote_lock *)ctx->userData;
+
+       if (tag_closed && ctx->cdata) {
+               if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) {
+                       lock->owner = xmalloc(strlen(ctx->cdata) + 1);
+                       strcpy(lock->owner, ctx->cdata);
+               } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TIMEOUT)) {
+                       if (!strncmp(ctx->cdata, "Second-", 7))
+                               lock->timeout =
+                                       strtol(ctx->cdata + 7, NULL, 10);
+               } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) {
+                       if (!strncmp(ctx->cdata, "opaquelocktoken:", 16)) {
+                               lock->token = xmalloc(strlen(ctx->cdata) - 15);
+                               strcpy(lock->token, ctx->cdata + 16);
+                       }
+               }
+       }
+}
+
+static void one_remote_ref(char *refname);
+
+static void
+xml_start_tag(void *userData, const char *name, const char **atts)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = strchr(name, ':');
+       int new_len;
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       new_len = strlen(ctx->name) + strlen(c) + 2;
+
+       if (new_len > ctx->len) {
+               ctx->name = xrealloc(ctx->name, new_len);
+               ctx->len = new_len;
+       }
+       strcat(ctx->name, ".");
+       strcat(ctx->name, c);
+
+       if (ctx->cdata) {
+               free(ctx->cdata);
+               ctx->cdata = NULL;
+       }
+
+       ctx->userFunc(ctx, 0);
+}
+
+static void
+xml_end_tag(void *userData, const char *name)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       const char *c = strchr(name, ':');
+       char *ep;
+
+       ctx->userFunc(ctx, 1);
+
+       if (c == NULL)
+               c = name;
+       else
+               c++;
+
+       ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
+       *ep = 0;
+}
+
+static void
+xml_cdata(void *userData, const XML_Char *s, int len)
+{
+       struct xml_ctx *ctx = (struct xml_ctx *)userData;
+       if (ctx->cdata)
+               free(ctx->cdata);
+       ctx->cdata = xcalloc(len+1, 1);
+       strncpy(ctx->cdata, s, len);
+}
+
+static struct remote_lock *lock_remote(char *path, long timeout)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer out_buffer;
+       struct buffer in_buffer;
+       char *out_data;
+       char *in_data;
+       char *url;
+       char *ep;
+       char timeout_header[25];
+       struct remote_lock *lock = NULL;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+
+       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", remote->url, path);
+
+       /* Make sure leading directories exist for the remote ref */
+       ep = strchr(url + strlen(remote->url) + 11, '/');
+       while (ep) {
+               *ep = 0;
+               slot = get_active_slot();
+               slot->results = &results;
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+               curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+               curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+               if (start_active_slot(slot)) {
+                       run_active_slot(slot);
+                       if (results.curl_result != CURLE_OK &&
+                           results.http_code != 405) {
+                               fprintf(stderr,
+                                       "Unable to create branch path %s\n",
+                                       url);
+                               free(url);
+                               return NULL;
+                       }
+               } else {
+                       fprintf(stderr, "Unable to start MKCOL request\n");
+                       free(url);
+                       return NULL;
+               }
+               *ep = '/';
+               ep = strchr(ep + 1, '/');
+       }
+
+       out_buffer.size = strlen(LOCK_REQUEST) + strlen(git_default_email) - 2;
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, LOCK_REQUEST, git_default_email);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       sprintf(timeout_header, "Timeout: Second-%ld", timeout);
+       dav_headers = curl_slist_append(dav_headers, timeout_header);
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       lock = xcalloc(1, sizeof(*lock));
+       lock->timeout = -1;
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_new_lock_ctx;
+                       ctx.userData = lock;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                               lock->timeout = -1;
+                       }
+               }
+       } else {
+               fprintf(stderr, "Unable to start LOCK request\n");
+       }
+
+       curl_slist_free_all(dav_headers);
+       free(out_data);
+       free(in_data);
+
+       if (lock->token == NULL || lock->timeout <= 0) {
+               if (lock->token != NULL)
+                       free(lock->token);
+               if (lock->owner != NULL)
+                       free(lock->owner);
+               free(url);
+               free(lock);
+               lock = NULL;
+       } else {
+               lock->url = url;
+               lock->start_time = time(NULL);
+               lock->next = remote->locks;
+               remote->locks = lock;
+       }
+
+       return lock;
+}
+
+static int unlock_remote(struct remote_lock *lock)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct remote_lock *prev = remote->locks;
+       char *lock_token_header;
+       struct curl_slist *dav_headers = NULL;
+       int rc = 0;
+
+       lock_token_header = xmalloc(strlen(lock->token) + 31);
+       sprintf(lock_token_header, "Lock-Token: <opaquelocktoken:%s>",
+               lock->token);
+       dav_headers = curl_slist_append(dav_headers, lock_token_header);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK)
+                       rc = 1;
+               else
+                       fprintf(stderr, "UNLOCK HTTP error %ld\n",
+                               results.http_code);
+       } else {
+               fprintf(stderr, "Unable to start UNLOCK request\n");
+       }
+
+       curl_slist_free_all(dav_headers);
+       free(lock_token_header);
+
+       if (remote->locks == lock) {
+               remote->locks = lock->next;
+       } else {
+               while (prev && prev->next != lock)
+                       prev = prev->next;
+               if (prev)
+                       prev->next = prev->next->next;
+       }
+
+       if (lock->owner != NULL)
+               free(lock->owner);
+       free(lock->url);
+       free(lock->token);
+       free(lock);
+
+       return rc;
+}
+
+static void remote_ls(const char *path, int flags,
+                     void (*userFunc)(struct remote_ls_ctx *ls),
+                     void *userData);
+
+static void process_ls_object(struct remote_ls_ctx *ls)
+{
+       unsigned int *parent = (unsigned int *)ls->userData;
+       char *path = ls->dentry_name;
+       char *obj_hex;
+
+       if (!strcmp(ls->path, ls->dentry_name) && (ls->flags & IS_DIR)) {
+               remote_dir_exists[*parent] = 1;
+               return;
+       }
+
+       if (strlen(path) != 49)
+               return;
+       path += 8;
+       obj_hex = xmalloc(strlen(path));
+       strncpy(obj_hex, path, 2);
+       strcpy(obj_hex + 2, path + 3);
+       one_remote_object(obj_hex);
+       free(obj_hex);
+}
+
+static void process_ls_ref(struct remote_ls_ctx *ls)
+{
+       if (!strcmp(ls->path, ls->dentry_name) && (ls->dentry_flags & IS_DIR)) {
+               fprintf(stderr, "  %s\n", ls->dentry_name);
+               return;
+       }
+
+       if (!(ls->dentry_flags & IS_DIR))
+               one_remote_ref(ls->dentry_name);
+}
+
+static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
+{
+       struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
+
+       if (tag_closed) {
+               if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
+                       if (ls->dentry_flags & IS_DIR) {
+                               if (ls->flags & PROCESS_DIRS) {
+                                       ls->userFunc(ls);
+                               }
+                               if (strcmp(ls->dentry_name, ls->path) &&
+                                   ls->flags & RECURSIVE) {
+                                       remote_ls(ls->dentry_name,
+                                                 ls->flags,
+                                                 ls->userFunc,
+                                                 ls->userData);
+                               }
+                       } else if (ls->flags & PROCESS_FILES) {
+                               ls->userFunc(ls);
+                       }
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
+                       ls->dentry_name = xmalloc(strlen(ctx->cdata) -
+                                                 remote->path_len + 1);
+                       strcpy(ls->dentry_name, ctx->cdata + remote->path_len);
+               } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
+                       ls->dentry_flags |= IS_DIR;
+               }
+       } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
+               if (ls->dentry_name) {
+                       free(ls->dentry_name);
+               }
+               ls->dentry_name = NULL;
+               ls->dentry_flags = 0;
+       }
+}
+
+static void remote_ls(const char *path, int flags,
+                     void (*userFunc)(struct remote_ls_ctx *ls),
+                     void *userData)
+{
+       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       struct remote_ls_ctx ls;
+
+       ls.flags = flags;
+       ls.path = strdup(path);
+       ls.dentry_name = NULL;
+       ls.dentry_flags = 0;
+       ls.userData = userData;
+       ls.userFunc = userFunc;
+
+       sprintf(url, "%s%s", remote->url, path);
+
+       out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 1");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_remote_ls_ctx;
+                       ctx.userData = &ls;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       XML_SetCharacterDataHandler(parser, xml_cdata);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                       }
+               }
+       } else {
+               fprintf(stderr, "Unable to start PROPFIND request\n");
+       }
+
+       free(ls.path);
+       free(url);
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
+}
+
+static void get_remote_object_list(unsigned char parent)
+{
+       char path[] = "objects/XX/";
+       static const char hex[] = "0123456789abcdef";
+       unsigned int val = parent;
+
+       path[8] = hex[val >> 4];
+       path[9] = hex[val & 0xf];
+       remote_dir_exists[val] = 0;
+       remote_ls(path, (PROCESS_FILES | PROCESS_DIRS),
+                 process_ls_object, &val);
+}
+
+static int locking_available(void)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       struct buffer in_buffer;
+       struct buffer out_buffer;
+       char *in_data;
+       char *out_data;
+       XML_Parser parser = XML_ParserCreate(NULL);
+       enum XML_Status result;
+       struct curl_slist *dav_headers = NULL;
+       struct xml_ctx ctx;
+       int lock_flags = 0;
+
+       out_buffer.size =
+               strlen(PROPFIND_SUPPORTEDLOCK_REQUEST) +
+               strlen(remote->url) - 2;
+       out_data = xmalloc(out_buffer.size + 1);
+       snprintf(out_data, out_buffer.size + 1,
+                PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       in_buffer.size = 4096;
+       in_data = xmalloc(in_buffer.size);
+       in_buffer.posn = 0;
+       in_buffer.buffer = in_data;
+
+       dav_headers = curl_slist_append(dav_headers, "Depth: 0");
+       dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result == CURLE_OK) {
+                       ctx.name = xcalloc(10, 1);
+                       ctx.len = 0;
+                       ctx.cdata = NULL;
+                       ctx.userFunc = handle_lockprop_ctx;
+                       ctx.userData = &lock_flags;
+                       XML_SetUserData(parser, &ctx);
+                       XML_SetElementHandler(parser, xml_start_tag,
+                                             xml_end_tag);
+                       result = XML_Parse(parser, in_buffer.buffer,
+                                          in_buffer.posn, 1);
+                       free(ctx.name);
+
+                       if (result != XML_STATUS_OK) {
+                               fprintf(stderr, "XML error: %s\n",
+                                       XML_ErrorString(
+                                               XML_GetErrorCode(parser)));
+                               lock_flags = 0;
+                       }
+               }
+       } else {
+               fprintf(stderr, "Unable to start PROPFIND request\n");
+       }
+
+       free(out_data);
+       free(in_buffer.buffer);
+       curl_slist_free_all(dav_headers);
+
+       return lock_flags;
+}
+
+static struct object_list **process_blob(struct blob *blob,
+                                        struct object_list **p,
+                                        struct name_path *path,
+                                        const char *name)
+{
+       struct object *obj = &blob->object;
+
+       obj->flags |= LOCAL;
+
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+
+       obj->flags |= SEEN;
+       name = strdup(name);
+       return add_object(obj, p, path, name);
+}
+
+static struct object_list **process_tree(struct tree *tree,
+                                        struct object_list **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;
+
+       obj->flags |= LOCAL;
+
+       if (obj->flags & (UNINTERESTING | SEEN))
+               return p;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+
+       obj->flags |= SEEN;
+       name = strdup(name);
+       p = add_object(obj, p, NULL, 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))
+                       p = process_tree(lookup_tree(entry.sha1), p, &me, name);
+               else
+                       p = process_blob(lookup_blob(entry.sha1), p, &me, name);
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+       return p;
+}
+
+static int get_delta(struct rev_info *revs, struct remote_lock *lock)
+{
+       struct commit *commit;
+       struct object_list **p = &objects, *pending;
+       int count = 0;
+
+       while ((commit = get_revision(revs)) != NULL) {
+               p = process_tree(commit->tree, p, NULL, "");
+               commit->object.flags |= LOCAL;
+               if (!(commit->object.flags & UNINTERESTING))
+                       count += add_send_request(&commit->object, lock);
+       }
+
+       for (pending = revs->pending_objects; pending; pending = pending->next) {
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+
+               if (obj->flags & (UNINTERESTING | SEEN))
+                       continue;
+               if (obj->type == tag_type) {
+                       obj->flags |= SEEN;
+                       p = add_object(obj, p, NULL, name);
+                       continue;
+               }
+               if (obj->type == tree_type) {
+                       p = process_tree((struct tree *)obj, p, NULL, name);
+                       continue;
+               }
+               if (obj->type == blob_type) {
+                       p = process_blob((struct blob *)obj, p, NULL, name);
+                       continue;
+               }
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+       }
+
+       while (objects) {
+               if (!(objects->item->flags & UNINTERESTING))
+                       count += add_send_request(objects->item, lock);
+               objects = objects->next;
+       }
+
+       return count;
+}
+
+static int update_remote(unsigned char *sha1, struct remote_lock *lock)
+{
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *out_data;
+       char *if_header;
+       struct buffer out_buffer;
+       struct curl_slist *dav_headers = NULL;
+       int i;
+
+       if_header = xmalloc(strlen(lock->token) + 25);
+       sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+       dav_headers = curl_slist_append(dav_headers, if_header);
+
+       out_buffer.size = 41;
+       out_data = xmalloc(out_buffer.size + 1);
+       i = snprintf(out_data, out_buffer.size + 1, "%s\n", sha1_to_hex(sha1));
+       if (i != out_buffer.size) {
+               fprintf(stderr, "Unable to initialize PUT request body\n");
+               return 0;
+       }
+       out_buffer.posn = 0;
+       out_buffer.buffer = out_data;
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               free(out_data);
+               free(if_header);
+               if (results.curl_result != CURLE_OK) {
+                       fprintf(stderr,
+                               "PUT error: curl result=%d, HTTP code=%ld\n",
+                               results.curl_result, results.http_code);
+                       /* We should attempt recovery? */
+                       return 0;
+               }
+       } else {
+               free(out_data);
+               free(if_header);
+               fprintf(stderr, "Unable to start PUT request\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+       struct ref *ref;
+       int len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->new_sha1, sha1, 20);
+       memcpy(ref->name, refname, len);
+       *local_tail = ref;
+       local_tail = &ref->next;
+       return 0;
+}
+
+static void one_remote_ref(char *refname)
+{
+       struct ref *ref;
+       unsigned char remote_sha1[20];
+       struct object *obj;
+       int len = strlen(refname) + 1;
+
+       if (fetch_ref(refname, remote_sha1) != 0) {
+               fprintf(stderr,
+                       "Unable to fetch ref %s from %s\n",
+                       refname, remote->url);
+               return;
+       }
+
+       /*
+        * Fetch a copy of the object if it doesn't exist locally - it
+        * may be required for updating server info later.
+        */
+       if (remote->can_update_info_refs && !has_sha1_file(remote_sha1)) {
+               obj = lookup_unknown_object(remote_sha1);
+               if (obj) {
+                       fprintf(stderr, "  fetch %s for %s\n",
+                               sha1_to_hex(remote_sha1), refname);
+                       add_fetch_request(obj);
+               }
+       }
+
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->old_sha1, remote_sha1, 20);
+       memcpy(ref->name, refname, len);
+       *remote_tail = ref;
+       remote_tail = &ref->next;
+}
+
+static void get_local_heads(void)
+{
+       local_tail = &local_refs;
+       for_each_ref(one_local_ref);
+}
+
+static void get_dav_remote_heads(void)
+{
+       remote_tail = &remote_refs;
+       remote_ls("refs/", (PROCESS_FILES | PROCESS_DIRS | RECURSIVE), process_ls_ref, NULL);
+}
+
+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 unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+                    const unsigned char *old_sha1)
+{
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       old = (struct commit *) o;
+
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       new = (struct commit *) o;
+
+       if (parse_commit(new) < 0)
+               return 0;
+
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, TMP_MARK);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, TMP_MARK);
+       unmark_and_free(used, TMP_MARK);
+       return found;
+}
+
+static void mark_edge_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents;
+
+       for (parents = commit->parents; parents; parents = parents->next) {
+               struct commit *parent = parents->item;
+               if (!(parent->object.flags & UNINTERESTING))
+                       continue;
+               mark_tree_uninteresting(parent->tree);
+       }
+}
+
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+       for ( ; list; list = list->next) {
+               struct commit *commit = list->item;
+
+               if (commit->object.flags & UNINTERESTING) {
+                       mark_tree_uninteresting(commit->tree);
+                       continue;
+               }
+               mark_edge_parents_uninteresting(commit);
+       }
+}
+
+static void add_remote_info_ref(struct remote_ls_ctx *ls)
+{
+       struct buffer *buf = (struct buffer *)ls->userData;
+       unsigned char remote_sha1[20];
+       struct object *o;
+       int len;
+       char *ref_info;
+
+       if (fetch_ref(ls->dentry_name, remote_sha1) != 0) {
+               fprintf(stderr,
+                       "Unable to fetch ref %s from %s\n",
+                       ls->dentry_name, remote->url);
+               aborted = 1;
+               return;
+       }
+
+       o = parse_object(remote_sha1);
+       if (!o) {
+               fprintf(stderr,
+                       "Unable to parse object %s for remote ref %s\n",
+                       sha1_to_hex(remote_sha1), ls->dentry_name);
+               aborted = 1;
+               return;
+       }
+
+       len = strlen(ls->dentry_name) + 42;
+       ref_info = xcalloc(len + 1, 1);
+       sprintf(ref_info, "%s   %s\n",
+               sha1_to_hex(remote_sha1), ls->dentry_name);
+       fwrite_buffer(ref_info, 1, len, buf);
+       free(ref_info);
+
+       if (o->type == tag_type) {
+               o = deref_tag(o, ls->dentry_name, 0);
+               if (o) {
+                       len = strlen(ls->dentry_name) + 45;
+                       ref_info = xcalloc(len + 1, 1);
+                       sprintf(ref_info, "%s   %s^{}\n",
+                               sha1_to_hex(o->sha1), ls->dentry_name);
+                       fwrite_buffer(ref_info, 1, len, buf);
+                       free(ref_info);
+               }
+       }
+}
+
+static void update_remote_info_refs(struct remote_lock *lock)
+{
+       struct buffer buffer;
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *if_header;
+       struct curl_slist *dav_headers = NULL;
+
+       buffer.buffer = xcalloc(1, 4096);
+       buffer.size = 4096;
+       buffer.posn = 0;
+       remote_ls("refs/", (PROCESS_FILES | RECURSIVE),
+                 add_remote_info_ref, &buffer);
+       if (!aborted) {
+               if_header = xmalloc(strlen(lock->token) + 25);
+               sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+               dav_headers = curl_slist_append(dav_headers, if_header);
+
+               slot = get_active_slot();
+               slot->results = &results;
+               curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
+               curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.posn);
+               curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+               curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+               curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+               curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+               curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+               curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+               buffer.posn = 0;
+
+               if (start_active_slot(slot)) {
+                       run_active_slot(slot);
+                       if (results.curl_result != CURLE_OK) {
+                               fprintf(stderr,
+                                       "PUT error: curl result=%d, HTTP code=%ld\n",
+                                       results.curl_result, results.http_code);
+                       }
+               }
+               free(if_header);
+       }
+       free(buffer.buffer);
+}
+
+static int remote_exists(const char *path)
+{
+       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       sprintf(url, "%s%s", remote->url, path);
+
+        slot = get_active_slot();
+       slot->results = &results;
+        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+        curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+
+        if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.http_code == 404)
+                       return 0;
+               else if (results.curl_result == CURLE_OK)
+                       return 1;
+               else
+                       fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code);
+       } else {
+               fprintf(stderr, "Unable to start HEAD request\n");
+       }
+
+       return -1;
+}
+
+static void fetch_symref(char *path, char **symref, unsigned char *sha1)
+{
+       char *url;
+       struct buffer buffer;
+       struct active_request_slot *slot;
+       struct slot_results results;
+
+       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", remote->url, path);
+
+       buffer.size = 4096;
+       buffer.posn = 0;
+       buffer.buffer = xmalloc(buffer.size);
+
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               if (results.curl_result != CURLE_OK) {
+                       die("Couldn't get %s for remote symref\n%s",
+                           url, curl_errorstr);
+               }
+       } else {
+               die("Unable to start remote symref request");
+       }
+       free(url);
+
+       if (*symref != NULL)
+               free(*symref);
+       *symref = NULL;
+       memset(sha1, 0, 20);
+
+       if (buffer.posn == 0)
+               return;
+
+       /* If it's a symref, set the refname; otherwise try for a sha1 */
+       if (!strncmp((char *)buffer.buffer, "ref: ", 5)) {
+               *symref = xcalloc(buffer.posn - 5, 1);
+               strncpy(*symref, (char *)buffer.buffer + 5, buffer.posn - 6);
+       } else {
+               get_sha1_hex(buffer.buffer, sha1);
+       }
+
+       free(buffer.buffer);
+}
+
+static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+{
+       int pipe_fd[2];
+       pid_t merge_base_pid;
+       char line[PATH_MAX + 20];
+       unsigned char merge_sha1[20];
+       int verified = 0;
+
+       if (pipe(pipe_fd) < 0)
+               die("Verify merge base: pipe failed");
+
+       merge_base_pid = fork();
+       if (!merge_base_pid) {
+               static const char *args[] = {
+                       "merge-base",
+                       "-a",
+                       NULL,
+                       NULL,
+                       NULL
+               };
+               args[2] = strdup(sha1_to_hex(head_sha1));
+               args[3] = sha1_to_hex(branch_sha1);
+
+               dup2(pipe_fd[1], 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               execv_git_cmd(args);
+               die("merge-base setup failed");
+       }
+       if (merge_base_pid < 0)
+               die("merge-base fork failed");
+
+       dup2(pipe_fd[0], 0);
+       close(pipe_fd[0]);
+       close(pipe_fd[1]);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               if (get_sha1_hex(line, merge_sha1))
+                       die("expected sha1, got garbage:\n %s", line);
+               if (!memcmp(branch_sha1, merge_sha1, 20)) {
+                       verified = 1;
+                       break;
+               }
+       }
+
+       return verified;
+}
+
+static int delete_remote_branch(char *pattern, int force)
+{
+       struct ref *refs = remote_refs;
+       struct ref *remote_ref = NULL;
+       unsigned char head_sha1[20];
+       char *symref = NULL;
+       int match;
+       int patlen = strlen(pattern);
+       int i;
+       struct active_request_slot *slot;
+       struct slot_results results;
+       char *url;
+
+       /* Find the remote branch(es) matching the specified branch name */
+       for (match = 0; refs; refs = refs->next) {
+               char *name = refs->name;
+               int namelen = strlen(name);
+               if (namelen < patlen ||
+                   memcmp(name + namelen - patlen, pattern, patlen))
+                       continue;
+               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+                       continue;
+               match++;
+               remote_ref = refs;
+       }
+       if (match == 0)
+               return error("No remote branch matches %s", pattern);
+       if (match != 1)
+               return error("More than one remote branch matches %s",
+                            pattern);
+
+       /*
+        * Remote HEAD must be a symref (not exactly foolproof; a remote
+        * symlink to a symref will look like a symref)
+        */
+       fetch_symref("HEAD", &symref, head_sha1);
+       if (!symref)
+               return error("Remote HEAD is not a symref");
+
+       /* Remote branch must not be the remote HEAD */
+       for (i=0; symref && i<MAXDEPTH; i++) {
+               if (!strcmp(remote_ref->name, symref))
+                       return error("Remote branch %s is the current HEAD",
+                                    remote_ref->name);
+               fetch_symref(symref, &symref, head_sha1);
+       }
+
+       /* Run extra sanity checks if delete is not forced */
+       if (!force) {
+               /* Remote HEAD must resolve to a known object */
+               if (symref)
+                       return error("Remote HEAD symrefs too deep");
+               if (is_zero_sha1(head_sha1))
+                       return error("Unable to resolve remote HEAD");
+               if (!has_sha1_file(head_sha1))
+                       return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1));
+
+               /* Remote branch must resolve to a known object */
+               if (is_zero_sha1(remote_ref->old_sha1))
+                       return error("Unable to resolve remote branch %s",
+                                    remote_ref->name);
+               if (!has_sha1_file(remote_ref->old_sha1))
+                       return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
+
+               /* Remote branch must be an ancestor of remote HEAD */
+               if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+                       return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+               }
+       }
+
+       /* Send delete request */
+       fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
+       url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
+       sprintf(url, "%s%s", remote->url, remote_ref->name);
+       slot = get_active_slot();
+       slot->results = &results;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_DELETE);
+       if (start_active_slot(slot)) {
+               run_active_slot(slot);
+               free(url);
+               if (results.curl_result != CURLE_OK)
+                       return error("DELETE request failed (%d/%ld)\n",
+                                    results.curl_result, results.http_code);
+       } else {
+               free(url);
+               return error("Unable to start DELETE request");
+       }
+
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       struct transfer_request *request;
+       struct transfer_request *next_request;
+       int nr_refspec = 0;
+       char **refspec = NULL;
+       struct remote_lock *ref_lock = NULL;
+       struct remote_lock *info_ref_lock = NULL;
+       struct rev_info revs;
+       int delete_branch = 0;
+       int force_delete = 0;
+       int objects_to_send;
+       int rc = 0;
+       int i;
+       int new_refs;
+       struct ref *ref;
+
+       setup_git_directory();
+       setup_ident();
+
+       remote = xcalloc(sizeof(*remote), 1);
+
+       argv++;
+       for (i = 1; i < argc; i++, argv++) {
+               char *arg = *argv;
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "--all")) {
+                               push_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--force")) {
+                               force_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verbose")) {
+                               push_verbosely = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-d")) {
+                               delete_branch = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-D")) {
+                               delete_branch = 1;
+                               force_delete = 1;
+                               continue;
+                       }
+               }
+               if (!remote->url) {
+                       char *path = strstr(arg, "//");
+                       remote->url = arg;
+                       if (path) {
+                               path = strchr(path+2, '/');
+                               if (path)
+                                       remote->path_len = strlen(path);
+                       }
+                       continue;
+               }
+               refspec = argv;
+               nr_refspec = argc - i;
+               break;
+       }
+
+       if (!remote->url)
+               usage(http_push_usage);
+
+       if (delete_branch && nr_refspec != 1)
+               die("You must specify only one branch name when deleting a remote branch");
+
+       memset(remote_dir_exists, -1, 256);
+
+       http_init();
+
+       no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+       default_headers = curl_slist_append(default_headers, "Range:");
+       default_headers = curl_slist_append(default_headers, "Destination:");
+       default_headers = curl_slist_append(default_headers, "If:");
+       default_headers = curl_slist_append(default_headers,
+                                           "Pragma: no-cache");
+
+       /* Verify DAV compliance/lock support */
+       if (!locking_available()) {
+               fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
+               rc = 1;
+               goto cleanup;
+       }
+
+       /* Check whether the remote has server info files */
+       remote->can_update_info_refs = 0;
+       remote->has_info_refs = remote_exists("info/refs");
+       remote->has_info_packs = remote_exists("objects/info/packs");
+       if (remote->has_info_refs) {
+               info_ref_lock = lock_remote("info/refs", LOCK_TIME);
+               if (info_ref_lock)
+                       remote->can_update_info_refs = 1;
+       }
+       if (remote->has_info_packs)
+               fetch_indices();
+
+       /* Get a list of all local and remote heads to validate refspecs */
+       get_local_heads();
+       fprintf(stderr, "Fetching remote heads...\n");
+       get_dav_remote_heads();
+
+       /* Remove a remote branch if -d or -D was specified */
+       if (delete_branch) {
+               if (delete_remote_branch(refspec[0], force_delete) == -1)
+                       fprintf(stderr, "Unable to delete remote branch %s\n",
+                               refspec[0]);
+               goto cleanup;
+       }
+
+       /* match them up */
+       if (!remote_tail)
+               remote_tail = &remote_refs;
+       if (match_refs(local_refs, remote_refs, &remote_tail,
+                      nr_refspec, refspec, push_all))
+               return -1;
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+               return 0;
+       }
+
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char old_hex[60], *new_hex;
+               const char *commit_argv[4];
+               int commit_argc;
+               char *new_sha1_hex, *old_sha1_hex;
+
+               if (!ref->peer_ref)
+                       continue;
+               if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+                       if (push_verbosely || 1)
+                               fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       continue;
+               }
+
+               if (!force_all &&
+                   !is_zero_sha1(ref->old_sha1) &&
+                   !ref->force) {
+                       if (!has_sha1_file(ref->old_sha1) ||
+                           !ref_newer(ref->peer_ref->new_sha1,
+                                      ref->old_sha1)) {
+                               /* We do not have the remote ref, or
+                                * we know that the remote ref is not
+                                * an ancestor of what we are trying to
+                                * push.  Either way this can be losing
+                                * commits at the remote end and likely
+                                * we were not up to date to begin with.
+                                */
+                               error("remote '%s' is not a strict "
+                                     "subset of local ref '%s'. "
+                                     "maybe you are not up-to-date and "
+                                     "need to pull first?",
+                                     ref->name,
+                                     ref->peer_ref->name);
+                               rc = -2;
+                               continue;
+                       }
+               }
+               memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+               if (is_zero_sha1(ref->new_sha1)) {
+                       error("cannot happen anymore");
+                       rc = -3;
+                       continue;
+               }
+               new_refs++;
+               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+               new_hex = sha1_to_hex(ref->new_sha1);
+
+               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);
+
+
+               /* Lock remote branch ref */
+               ref_lock = lock_remote(ref->name, LOCK_TIME);
+               if (ref_lock == NULL) {
+                       fprintf(stderr, "Unable to lock remote branch %s\n",
+                               ref->name);
+                       rc = 1;
+                       continue;
+               }
+
+               /* Set up revision info for this refspec */
+               commit_argc = 3;
+               new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1));
+               old_sha1_hex = NULL;
+               commit_argv[1] = "--objects";
+               commit_argv[2] = new_sha1_hex;
+               if (!push_all && !is_zero_sha1(ref->old_sha1)) {
+                       old_sha1_hex = xmalloc(42);
+                       sprintf(old_sha1_hex, "^%s",
+                               sha1_to_hex(ref->old_sha1));
+                       commit_argv[3] = old_sha1_hex;
+                       commit_argc++;
+               }
+               init_revisions(&revs);
+               setup_revisions(commit_argc, commit_argv, &revs, NULL);
+               free(new_sha1_hex);
+               if (old_sha1_hex) {
+                       free(old_sha1_hex);
+                       commit_argv[1] = NULL;
+               }
+
+               /* Generate a list of objects that need to be pushed */
+               pushing = 0;
+               prepare_revision_walk(&revs);
+               mark_edges_uninteresting(revs.commits);
+               objects_to_send = get_delta(&revs, ref_lock);
+               finish_all_active_slots();
+
+               /* Push missing objects to remote, this would be a
+                  convenient time to pack them first if appropriate. */
+               pushing = 1;
+               if (objects_to_send)
+                       fprintf(stderr, "    sending %d objects\n",
+                               objects_to_send);
+#ifdef USE_CURL_MULTI
+               fill_active_slots();
+#endif
+               finish_all_active_slots();
+
+               /* Update the remote branch if all went well */
+               if (aborted || !update_remote(ref->new_sha1, ref_lock)) {
+                       rc = 1;
+                       goto unlock;
+               }
+
+       unlock:
+               if (!rc)
+                       fprintf(stderr, "    done\n");
+               unlock_remote(ref_lock);
+               check_locks();
+       }
+
+       /* Update remote server info if appropriate */
+       if (remote->has_info_refs && new_refs) {
+               if (info_ref_lock && remote->can_update_info_refs) {
+                       fprintf(stderr, "Updating remote server info\n");
+                       update_remote_info_refs(info_ref_lock);
+               } else {
+                       fprintf(stderr, "Unable to update server info\n");
+               }
+       }
+       if (info_ref_lock)
+               unlock_remote(info_ref_lock);
+
+ cleanup:
+       free(remote);
+
+       curl_slist_free_all(no_pragma_header);
+       curl_slist_free_all(default_headers);
+
+       http_cleanup();
+
+       request = request_queue_head;
+       while (request != NULL) {
+               next_request = request->next;
+               release_request(request);
+               request = next_request;
+       }
+
+       return rc;
+}
diff --git a/http.c b/http.c
new file mode 100644 (file)
index 0000000..08769cc
--- /dev/null
+++ b/http.c
@@ -0,0 +1,484 @@
+#include "http.h"
+
+int data_received;
+int active_requests = 0;
+
+#ifdef USE_CURL_MULTI
+int max_requests = -1;
+CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+CURL *curl_default;
+#endif
+char curl_errorstr[CURL_ERROR_SIZE];
+
+int curl_ssl_verify = -1;
+char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+char *ssl_capath = NULL;
+#endif
+char *ssl_cainfo = NULL;
+long curl_low_speed_limit = -1;
+long curl_low_speed_time = -1;
+
+struct curl_slist *pragma_header;
+
+struct active_request_slot *active_queue_head = NULL;
+
+size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+                          struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn)
+               size = buffer->size - buffer->posn;
+       memcpy(ptr, buffer->buffer + buffer->posn, size);
+       buffer->posn += size;
+       return size;
+}
+
+size_t fwrite_buffer(const void *ptr, size_t eltsize,
+                           size_t nmemb, struct buffer *buffer)
+{
+       size_t size = eltsize * nmemb;
+       if (size > buffer->size - buffer->posn) {
+               buffer->size = buffer->size * 3 / 2;
+               if (buffer->size < buffer->posn + size)
+                       buffer->size = buffer->posn + size;
+               buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+       }
+       memcpy(buffer->buffer + buffer->posn, ptr, size);
+       buffer->posn += size;
+       data_received++;
+       return size;
+}
+
+size_t fwrite_null(const void *ptr, size_t eltsize,
+                         size_t nmemb, struct buffer *buffer)
+{
+       data_received++;
+       return eltsize * nmemb;
+}
+
+static void finish_active_slot(struct active_request_slot *slot);
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void)
+{
+       int num_messages;
+       struct active_request_slot *slot;
+       CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+       while (curl_message != NULL) {
+               if (curl_message->msg == CURLMSG_DONE) {
+                       int curl_result = curl_message->data.result;
+                       slot = active_queue_head;
+                       while (slot != NULL &&
+                              slot->curl != curl_message->easy_handle)
+                               slot = slot->next;
+                       if (slot != NULL) {
+                               curl_multi_remove_handle(curlm, slot->curl);
+                               slot->curl_result = curl_result;
+                               finish_active_slot(slot);
+                       } else {
+                               fprintf(stderr, "Received DONE message for unknown request!\n");
+                       }
+               } else {
+                       fprintf(stderr, "Unknown CURL message received: %d\n",
+                               (int)curl_message->msg);
+               }
+               curl_message = curl_multi_info_read(curlm, &num_messages);
+       }
+}
+#endif
+
+static int http_options(const char *var, const char *value)
+{
+       if (!strcmp("http.sslverify", var)) {
+               if (curl_ssl_verify == -1) {
+                       curl_ssl_verify = git_config_bool(var, value);
+               }
+               return 0;
+       }
+
+       if (!strcmp("http.sslcert", var)) {
+               if (ssl_cert == NULL) {
+                       ssl_cert = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cert, value);
+               }
+               return 0;
+       }
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (!strcmp("http.sslkey", var)) {
+               if (ssl_key == NULL) {
+                       ssl_key = xmalloc(strlen(value)+1);
+                       strcpy(ssl_key, value);
+               }
+               return 0;
+       }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (!strcmp("http.sslcapath", var)) {
+               if (ssl_capath == NULL) {
+                       ssl_capath = xmalloc(strlen(value)+1);
+                       strcpy(ssl_capath, value);
+               }
+               return 0;
+       }
+#endif
+       if (!strcmp("http.sslcainfo", var)) {
+               if (ssl_cainfo == NULL) {
+                       ssl_cainfo = xmalloc(strlen(value)+1);
+                       strcpy(ssl_cainfo, value);
+               }
+               return 0;
+       }
+
+#ifdef USE_CURL_MULTI  
+       if (!strcmp("http.maxrequests", var)) {
+               if (max_requests == -1)
+                       max_requests = git_config_int(var, value);
+               return 0;
+       }
+#endif
+
+       if (!strcmp("http.lowspeedlimit", var)) {
+               if (curl_low_speed_limit == -1)
+                       curl_low_speed_limit = (long)git_config_int(var, value);
+               return 0;
+       }
+       if (!strcmp("http.lowspeedtime", var)) {
+               if (curl_low_speed_time == -1)
+                       curl_low_speed_time = (long)git_config_int(var, value);
+               return 0;
+       }
+
+       /* Fall back on the default ones */
+       return git_default_config(var, value);
+}
+
+static CURL* get_curl_handle(void)
+{
+       CURL* result = curl_easy_init();
+
+       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+       curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+       if (ssl_cert != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+       if (ssl_key != NULL)
+               curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       if (ssl_capath != NULL)
+               curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+       if (ssl_cainfo != NULL)
+               curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+       curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+       if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+                                curl_low_speed_limit);
+               curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+                                curl_low_speed_time);
+       }
+
+       curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+
+       if (getenv("GIT_CURL_VERBOSE"))
+               curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
+
+       curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
+
+       return result;
+}
+
+void http_init(void)
+{
+       char *low_speed_limit;
+       char *low_speed_time;
+
+       curl_global_init(CURL_GLOBAL_ALL);
+
+       pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
+
+#ifdef USE_CURL_MULTI
+       {
+               char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+               if (http_max_requests != NULL)
+                       max_requests = atoi(http_max_requests);
+       }
+
+       curlm = curl_multi_init();
+       if (curlm == NULL) {
+               fprintf(stderr, "Error creating curl multi handle.\n");
+               exit(1);
+       }
+#endif
+
+       if (getenv("GIT_SSL_NO_VERIFY"))
+               curl_ssl_verify = 0;
+
+       ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+       ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+       ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+       ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+       low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+       if (low_speed_limit != NULL)
+               curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+       low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+       if (low_speed_time != NULL)
+               curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+       git_config(http_options);
+
+       if (curl_ssl_verify == -1)
+               curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+       if (max_requests < 1)
+               max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_default = get_curl_handle();
+#endif
+}
+
+void http_cleanup(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+#ifdef USE_CURL_MULTI
+       char *wait_url;
+#endif
+
+       while (slot != NULL) {
+#ifdef USE_CURL_MULTI
+               if (slot->in_use) {
+                       curl_easy_getinfo(slot->curl,
+                                         CURLINFO_EFFECTIVE_URL,
+                                         &wait_url);
+                       fprintf(stderr, "Waiting for %s\n", wait_url);
+                       run_active_slot(slot);
+               }
+#endif
+               if (slot->curl != NULL)
+                       curl_easy_cleanup(slot->curl);
+               slot = slot->next;
+       }
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+       curl_easy_cleanup(curl_default);
+#endif
+
+#ifdef USE_CURL_MULTI
+       curl_multi_cleanup(curlm);
+#endif
+       curl_global_cleanup();
+
+       curl_slist_free_all(pragma_header);
+}
+
+struct active_request_slot *get_active_slot(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+       struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+       int num_transfers;
+
+       /* Wait for a slot to open up if the queue is full */
+       while (active_requests >= max_requests) {
+               curl_multi_perform(curlm, &num_transfers);
+               if (num_transfers < active_requests) {
+                       process_curl_messages();
+               }
+       }
+#endif
+
+       while (slot != NULL && slot->in_use) {
+               slot = slot->next;
+       }
+       if (slot == NULL) {
+               newslot = xmalloc(sizeof(*newslot));
+               newslot->curl = NULL;
+               newslot->in_use = 0;
+               newslot->next = NULL;
+
+               slot = active_queue_head;
+               if (slot == NULL) {
+                       active_queue_head = newslot;
+               } else {
+                       while (slot->next != NULL) {
+                               slot = slot->next;
+                       }
+                       slot->next = newslot;
+               }
+               slot = newslot;
+       }
+
+       if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+               slot->curl = get_curl_handle();
+#else
+               slot->curl = curl_easy_duphandle(curl_default);
+#endif
+       }
+
+       active_requests++;
+       slot->in_use = 1;
+       slot->local = NULL;
+       slot->results = NULL;
+       slot->finished = NULL;
+       slot->callback_data = NULL;
+       slot->callback_func = NULL;
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
+       curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+       curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL);
+       curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0);
+       curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+
+       return slot;
+}
+
+int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+       if (curlm_result != CURLM_OK &&
+           curlm_result != CURLM_CALL_MULTI_PERFORM) {
+               active_requests--;
+               slot->in_use = 0;
+               return 0;
+       }
+#endif
+       return 1;
+}
+
+#ifdef USE_CURL_MULTI
+void step_active_slots(void)
+{
+       int num_transfers;
+       CURLMcode curlm_result;
+
+       do {
+               curlm_result = curl_multi_perform(curlm, &num_transfers);
+       } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+       if (num_transfers < active_requests) {
+               process_curl_messages();
+               fill_active_slots();
+       }
+}
+#endif
+
+void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+       long last_pos = 0;
+       long current_pos;
+       fd_set readfds;
+       fd_set writefds;
+       fd_set excfds;
+       int max_fd;
+       struct timeval select_timeout;
+       int finished = 0;
+
+       slot->finished = &finished;
+       while (!finished) {
+               data_received = 0;
+               step_active_slots();
+
+               if (!data_received && slot->local != NULL) {
+                       current_pos = ftell(slot->local);
+                       if (current_pos > last_pos)
+                               data_received++;
+                       last_pos = current_pos;
+               }
+
+               if (slot->in_use && !data_received) {
+                       max_fd = 0;
+                       FD_ZERO(&readfds);
+                       FD_ZERO(&writefds);
+                       FD_ZERO(&excfds);
+                       select_timeout.tv_sec = 0;
+                       select_timeout.tv_usec = 50000;
+                       select(max_fd, &readfds, &writefds,
+                              &excfds, &select_timeout);
+               }
+       }
+#else
+       while (slot->in_use) {
+               slot->curl_result = curl_easy_perform(slot->curl);
+               finish_active_slot(slot);
+       }
+#endif
+}
+
+static void closedown_active_slot(struct active_request_slot *slot)
+{
+        active_requests--;
+        slot->in_use = 0;
+}
+
+void release_active_slot(struct active_request_slot *slot)
+{
+       closedown_active_slot(slot);
+       if (slot->curl) {
+#ifdef USE_CURL_MULTI
+               curl_multi_remove_handle(curlm, slot->curl);
+#endif
+               curl_easy_cleanup(slot->curl);
+               slot->curl = NULL;
+       }
+#ifdef USE_CURL_MULTI
+       fill_active_slots();
+#endif
+}
+
+static void finish_active_slot(struct active_request_slot *slot)
+{
+       closedown_active_slot(slot);
+        curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
+
+       if (slot->finished != NULL)
+               (*slot->finished) = 1;
+
+       /* Store slot results so they can be read after the slot is reused */
+       if (slot->results != NULL) {
+               slot->results->curl_result = slot->curl_result;
+               slot->results->http_code = slot->http_code;
+       }
+
+        /* Run callback if appropriate */
+        if (slot->callback_func != NULL) {
+                slot->callback_func(slot->callback_data);
+        }
+}
+
+void finish_all_active_slots(void)
+{
+       struct active_request_slot *slot = active_queue_head;
+
+       while (slot != NULL)
+               if (slot->in_use) {
+                       run_active_slot(slot);
+                       slot = active_queue_head;
+               } else {
+                       slot = slot->next;
+               }
+}
diff --git a/http.h b/http.h
new file mode 100644 (file)
index 0000000..9ca16ac
--- /dev/null
+++ b/http.h
@@ -0,0 +1,104 @@
+#ifndef HTTP_H
+#define HTTP_H
+
+#include "cache.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070704
+#define curl_global_cleanup() do { /* nothing */ } while(0)
+#endif
+#if LIBCURL_VERSION_NUM < 0x070800
+#define curl_global_init(a) do { /* nothing */ } while(0)
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
+struct slot_results
+{
+       CURLcode curl_result;
+       long http_code;
+};
+
+struct active_request_slot
+{
+       CURL *curl;
+       FILE *local;
+       int in_use;
+       CURLcode curl_result;
+       long http_code;
+       int *finished;
+       struct slot_results *results;
+       void *callback_data;
+       void (*callback_func)(void *data);
+       struct active_request_slot *next;
+};
+
+struct buffer
+{
+        size_t posn;
+        size_t size;
+        void *buffer;
+};
+
+/* Curl request read/write callbacks */
+extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+                          struct buffer *buffer);
+extern size_t fwrite_buffer(const void *ptr, size_t eltsize,
+                           size_t nmemb, struct buffer *buffer);
+extern size_t fwrite_null(const void *ptr, size_t eltsize,
+                         size_t nmemb, struct buffer *buffer);
+
+/* Slot lifecycle functions */
+extern struct active_request_slot *get_active_slot(void);
+extern int start_active_slot(struct active_request_slot *slot);
+extern void run_active_slot(struct active_request_slot *slot);
+extern void finish_all_active_slots(void);
+extern void release_active_slot(struct active_request_slot *slot);
+
+#ifdef USE_CURL_MULTI
+extern void fill_active_slots(void);
+extern void step_active_slots(void);
+#endif
+
+extern void http_init(void);
+extern void http_cleanup(void);
+
+extern int data_received;
+extern int active_requests;
+
+#ifdef USE_CURL_MULTI
+extern int max_requests;
+extern CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+extern CURL *curl_default;
+#endif
+extern char curl_errorstr[CURL_ERROR_SIZE];
+
+extern int curl_ssl_verify;
+extern char *ssl_cert;
+#if LIBCURL_VERSION_NUM >= 0x070902
+extern char *ssl_key;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+extern char *ssl_capath;
+#endif
+extern char *ssl_cainfo;
+extern long curl_low_speed_limit;
+extern long curl_low_speed_time;
+
+extern struct curl_slist *pragma_header;
+extern struct curl_slist *no_range_header;
+
+extern struct active_request_slot *active_queue_head;
+
+#endif /* HTTP_H */
diff --git a/ident.c b/ident.c
new file mode 100644 (file)
index 0000000..7c81fe8
--- /dev/null
+++ b/ident.c
@@ -0,0 +1,219 @@
+/*
+ * ident.c
+ *
+ * create git identifier lines of the form "name <email> date"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ */
+#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)
+{
+       char *src, *dst;
+       int len, nlen;
+
+       nlen = strlen(w->pw_name);
+
+       /* Traditionally GECOS field had office phone numbers etc, separated
+        * with commas.  Also & stands for capitalized form of the login name.
+        */
+
+       for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) {
+               int ch = *src;
+               if (ch != '&') {
+                       *dst++ = ch;
+                       if (ch == 0 || ch == ',')
+                               break;
+                       len++;
+                       continue;
+               }
+               if (len + nlen < sz) {
+                       /* Sorry, Mr. McDonald... */
+                       *dst++ = toupper(*w->pw_name);
+                       memcpy(dst, w->pw_name + 1, nlen - 1);
+                       dst += nlen - 1;
+               }
+       }
+       if (len < sz)
+               name[len] = 0;
+       else
+               die("Your parents must have hated you!");
+
+}
+
+int setup_ident(void)
+{
+       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);
+       if (len > sizeof(git_default_email)/2)
+               die("Your sysadmin must hate you!");
+       memcpy(git_default_email, pw->pw_name, len);
+       git_default_email[len++] = '@';
+       gethostname(git_default_email + len, sizeof(git_default_email) - len);
+       if (!strchr(git_default_email+len, '.')) {
+               struct hostent *he = gethostbyname(git_default_email + len);
+               char *domainname;
+
+               len = strlen(git_default_email);
+               git_default_email[len++] = '.';
+               if (he && (domainname = strchr(he->h_name, '.')))
+                       strncpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len);
+               else
+                       strncpy(git_default_email + len, "(none)", sizeof(git_default_email) - len);
+               git_default_email[sizeof(git_default_email) - 1] = 0;
+       }
+       /* And set the default date */
+       datestamp(git_default_date, sizeof(git_default_date));
+       return 0;
+}
+
+static int add_raw(char *buf, int size, int offset, const char *str)
+{
+       int len = strlen(str);
+       if (offset + len > size)
+               return size;
+       memcpy(buf + offset, str, len);
+       return offset + len;
+}
+
+static int crud(unsigned char c)
+{
+       static char crud_array[256];
+       static int crud_array_initialized = 0;
+
+       if (!crud_array_initialized) {
+               int k;
+
+               for (k = 0; k <= 31; ++k) crud_array[k] = 1;
+               crud_array[' '] = 1;
+               crud_array['.'] = 1;
+               crud_array[','] = 1;
+               crud_array[':'] = 1;
+               crud_array[';'] = 1;
+               crud_array['<'] = 1;
+               crud_array['>'] = 1;
+               crud_array['"'] = 1;
+               crud_array['\''] = 1;
+               crud_array_initialized = 1;
+       }
+       return crud_array[c];
+}
+
+/*
+ * Copy over a string to the destination, but avoid special
+ * characters ('\n', '<' and '>') and remove crud at the end
+ */
+static int copy(char *buf, int size, int offset, const char *src)
+{
+       int i, len;
+       unsigned char c;
+
+       /* Remove crud from the beginning.. */
+       while ((c = *src) != 0) {
+               if (!crud(c))
+                       break;
+               src++;
+       }
+
+       /* Remove crud from the end.. */
+       len = strlen(src);
+       while (len > 0) {
+               c = src[len-1];
+               if (!crud(c))
+                       break;
+               --len;
+       }
+
+       /*
+        * Copy the rest to the buffer, but avoid the special
+        * characters '\n' '<' and '>' that act as delimiters on
+        * a identification line
+        */
+       for (i = 0; i < len; i++) {
+               c = *src++;
+               switch (c) {
+               case '\n': case '<': case '>':
+                       continue;
+               }
+               if (offset >= size)
+                       return size;
+               buf[offset++] = c;
+       }
+       return offset;
+}
+
+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"
+"*** 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)
+{
+       static char buffer[1000];
+       char date[50];
+       int i;
+
+       if (!name)
+               name = git_default_name;
+       if (!email)
+               email = git_default_email;
+
+       if (!*name) {
+               if (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)
+                       die("empty ident %s <%s> not allowed", name, email);
+       }
+
+       strcpy(date, git_default_date);
+       if (date_str)
+               parse_date(date_str, date, sizeof(date));
+
+       i = copy(buffer, sizeof(buffer), 0, name);
+       i = add_raw(buffer, sizeof(buffer), i, " <");
+       i = copy(buffer, sizeof(buffer), i, email);
+       i = add_raw(buffer, sizeof(buffer), i, "> ");
+       i = copy(buffer, sizeof(buffer), i, date);
+       if (i >= sizeof(buffer))
+               die("Impossibly long personal identifier");
+       buffer[i] = 0;
+       return buffer;
+}
+
+const char *git_author_info(int error_on_no_name)
+{
+       return get_ident(getenv("GIT_AUTHOR_NAME"),
+                        getenv("GIT_AUTHOR_EMAIL"),
+                        getenv("GIT_AUTHOR_DATE"),
+                        error_on_no_name);
+}
+
+const char *git_committer_info(int error_on_no_name)
+{
+       return get_ident(getenv("GIT_COMMITTER_NAME"),
+                        getenv("GIT_COMMITTER_EMAIL"),
+                        getenv("GIT_COMMITTER_DATE"),
+                        error_on_no_name);
+}
diff --git a/imap-send.c b/imap-send.c
new file mode 100644 (file)
index 0000000..52e2400
--- /dev/null
@@ -0,0 +1,1365 @@
+/*
+ * git-imap-send - drops patches into an imap Drafts folder
+ *                 derived from isync/mbsync - mailbox synchronizer
+ *
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
+ * Copyright (C) 2006 Mike McCormack
+ *
+ *  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
+ */
+
+#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 */
+       char *map_inbox;
+       char *trash;
+       unsigned max_size; /* off_t is overkill */
+       unsigned trash_remote_new:1, trash_only_new:1;
+} store_conf_t;
+
+typedef struct string_list {
+       struct string_list *next;
+       char string[1];
+} string_list_t;
+
+typedef struct channel_conf {
+       struct channel_conf *next;
+       char *name;
+       store_conf_t *master, *slave;
+       char *master_name, *slave_name;
+       char *sync_state;
+       string_list_t *patterns;
+       int mops, sops;
+       unsigned max_messages; /* for slave only */
+} channel_conf_t;
+
+typedef struct group_conf {
+       struct group_conf *next;
+       char *name;
+       string_list_t *channels;
+} group_conf_t;
+
+/* For message->status */
+#define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
+#define M_DEAD         (1<<1) /* expunged */
+#define M_FLAGS        (1<<2) /* flags fetched */
+
+typedef struct message {
+       struct message *next;
+       /* string_list_t *keywords; */
+       size_t size; /* zero implies "not fetched" */
+       int uid;
+       unsigned char flags, status;
+} message_t;
+
+typedef struct store {
+       store_conf_t *conf; /* foreign */
+
+       /* currently open mailbox */
+       const char *name; /* foreign! maybe preset? */
+       char *path; /* own */
+       message_t *msgs; /* own */
+       int uidvalidity;
+       unsigned char opts; /* maybe preset? */
+       /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
+       int count; /* # of messages */
+       int recent; /* # of recent messages - don't trust this beyond the initial read */
+} store_t;
+
+typedef struct {
+       char *data;
+       int len;
+       unsigned char flags;
+       unsigned char crlf:1;
+} msg_data_t;
+
+#define DRV_OK          0
+#define DRV_MSG_BAD     -1
+#define DRV_BOX_BAD     -2
+#define DRV_STORE_BAD   -3
+
+static int Verbose, Quiet;
+
+static void info( const char *, ... );
+static void warn( const char *, ... );
+
+static char *next_arg( char ** );
+
+static void free_generic_messages( message_t * );
+
+static int nfvasprintf( char **str, const char *fmt, va_list va );
+static int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+
+
+static void arc4_init( void );
+static unsigned char arc4_getbyte( void );
+
+typedef struct imap_server_conf {
+       char *name;
+       char *tunnel;
+       char *host;
+       int port;
+       char *user;
+       char *pass;
+} imap_server_conf_t;
+
+typedef struct imap_store_conf {
+       store_conf_t gen;
+       imap_server_conf_t *server;
+       unsigned use_namespace:1;
+} imap_store_conf_t;
+
+#define NIL    (void*)0x1
+#define LIST   (void*)0x2
+
+typedef struct _list {
+       struct _list *next, *child;
+       char *val;
+       int len;
+} list_t;
+
+typedef struct {
+       int fd;
+} Socket_t;
+
+typedef struct {
+       Socket_t sock;
+       int bytes;
+       int offset;
+       char buf[1024];
+} buffer_t;
+
+struct imap_cmd;
+
+typedef struct imap {
+       int uidnext; /* from SELECT responses */
+       list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+       unsigned caps, rcaps; /* CAPABILITY results */
+       /* command queue */
+       int nexttag, num_in_progress, literal_pending;
+       struct imap_cmd *in_progress, **in_progress_append;
+       buffer_t buf; /* this is BIG, so put it last */
+} imap_t;
+
+typedef struct imap_store {
+       store_t gen;
+       int uidvalidity;
+       imap_t *imap;
+       const char *prefix;
+       unsigned /*currentnc:1,*/ trashnc:1;
+} imap_store_t;
+
+struct imap_cmd_cb {
+       int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
+       void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response);
+       void *ctx;
+       char *data;
+       int dlen;
+       int uid;
+       unsigned create:1, trycreate:1;
+};
+
+struct imap_cmd {
+       struct imap_cmd *next;
+       struct imap_cmd_cb cb;
+       char *cmd;
+       int tag;
+};
+
+#define CAP(cap) (imap->caps & (1 << (cap)))
+
+enum CAPABILITY {
+       NOLOGIN = 0,
+       UIDPLUS,
+       LITERALPLUS,
+       NAMESPACE,
+};
+
+static const char *cap_list[] = {
+       "LOGINDISABLED",
+       "UIDPLUS",
+       "LITERAL+",
+       "NAMESPACE",
+};
+
+#define RESP_OK    0
+#define RESP_NO    1
+#define RESP_BAD   2
+
+static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
+
+
+static const char *Flags[] = {
+       "Draft",
+       "Flagged",
+       "Answered",
+       "Seen",
+       "Deleted",
+};
+
+static void
+socket_perror( const char *func, Socket_t *sock, int ret )
+{
+       if (ret < 0)
+               perror( func );
+       else
+               fprintf( stderr, "%s: unexpected EOF\n", func );
+}
+
+static int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+       int n = read( sock->fd, buf, len );
+       if (n <= 0) {
+               socket_perror( "read", sock, n );
+               close( sock->fd );
+               sock->fd = -1;
+       }
+       return n;
+}
+
+static int
+socket_write( Socket_t *sock, char *buf, int len )
+{
+       int n = write( sock->fd, buf, len );
+       if (n != len) {
+               socket_perror( "write", sock, n );
+               close( sock->fd );
+               sock->fd = -1;
+       }
+       return n;
+}
+
+/* simple line buffering */
+static int
+buffer_gets( buffer_t * b, char **s )
+{
+       int n;
+       int start = b->offset;
+
+       *s = b->buf + start;
+
+       for (;;) {
+               /* make sure we have enough data to read the \r\n sequence */
+               if (b->offset + 1 >= b->bytes) {
+                       if (start) {
+                               /* shift down used bytes */
+                               *s = b->buf;
+
+                               assert( start <= b->bytes );
+                               n = b->bytes - start;
+
+                               if (n)
+                                       memcpy( b->buf, b->buf + start, n );
+                               b->offset -= start;
+                               b->bytes = n;
+                               start = 0;
+                       }
+
+                       n = socket_read( &b->sock, b->buf + b->bytes,
+                                        sizeof(b->buf) - b->bytes );
+
+                       if (n <= 0)
+                               return -1;
+
+                       b->bytes += n;
+               }
+
+               if (b->buf[b->offset] == '\r') {
+                       assert( b->offset + 1 < b->bytes );
+                       if (b->buf[b->offset + 1] == '\n') {
+                               b->buf[b->offset] = 0;  /* terminate the string */
+                               b->offset += 2; /* next line */
+                               if (Verbose)
+                                       puts( *s );
+                               return 0;
+                       }
+               }
+
+               b->offset++;
+       }
+       /* not reached */
+}
+
+static void
+info( const char *msg, ... )
+{
+       va_list va;
+
+       if (!Quiet) {
+               va_start( va, msg );
+               vprintf( msg, va );
+               va_end( va );
+               fflush( stdout );
+       }
+}
+
+static void
+warn( const char *msg, ... )
+{
+       va_list va;
+
+       if (Quiet < 2) {
+               va_start( va, msg );
+               vfprintf( stderr, msg, va );
+               va_end( va );
+       }
+}
+
+static char *
+next_arg( char **s )
+{
+       char *ret;
+
+       if (!s || !*s)
+               return NULL;
+       while (isspace( (unsigned char) **s ))
+               (*s)++;
+       if (!**s) {
+               *s = NULL;
+               return NULL;
+       }
+       if (**s == '"') {
+               ++*s;
+               ret = *s;
+               *s = strchr( *s, '"' );
+       } else {
+               ret = *s;
+               while (**s && !isspace( (unsigned char) **s ))
+                       (*s)++;
+       }
+       if (*s) {
+               if (**s)
+                       *(*s)++ = 0;
+               if (!**s)
+                       *s = NULL;
+       }
+       return ret;
+}
+
+static void
+free_generic_messages( message_t *msgs )
+{
+       message_t *tmsg;
+
+       for (; msgs; msgs = tmsg) {
+               tmsg = msgs->next;
+               free( msgs );
+       }
+}
+
+static int
+git_vasprintf( char **strp, const char *fmt, va_list ap )
+{
+       int len;
+       char tmp[1024];
+
+       if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 )))
+               return -1;
+       if (len >= (int)sizeof(tmp))
+               vsprintf( *strp, fmt, ap );
+       else
+               memcpy( *strp, tmp, len + 1 );
+       return len;
+}
+
+static int
+nfsnprintf( char *buf, int blen, const char *fmt, ... )
+{
+       int ret;
+       va_list va;
+
+       va_start( va, fmt );
+       if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen)
+               die( "Fatal: buffer too small. Please report a bug.\n");
+       va_end( va );
+       return ret;
+}
+
+static int
+nfvasprintf( char **str, const char *fmt, va_list va )
+{
+       int ret = git_vasprintf( str, fmt, va );
+       if (ret < 0)
+               die( "Fatal: Out of memory\n");
+       return ret;
+}
+
+static struct {
+       unsigned char i, j, s[256];
+} rs;
+
+static void
+arc4_init( void )
+{
+       int i, fd;
+       unsigned char j, si, dat[128];
+
+       if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) {
+               fprintf( stderr, "Fatal: no random number source available.\n" );
+               exit( 3 );
+       }
+       if (read( fd, dat, 128 ) != 128) {
+               fprintf( stderr, "Fatal: cannot read random number source.\n" );
+               exit( 3 );
+       }
+       close( fd );
+
+       for (i = 0; i < 256; i++)
+               rs.s[i] = i;
+       for (i = j = 0; i < 256; i++) {
+               si = rs.s[i];
+               j += si + dat[i & 127];
+               rs.s[i] = rs.s[j];
+               rs.s[j] = si;
+       }
+       rs.i = rs.j = 0;
+
+       for (i = 0; i < 256; i++)
+               arc4_getbyte();
+}
+
+static unsigned char
+arc4_getbyte( void )
+{
+       unsigned char si, sj;
+
+       rs.i++;
+       si = rs.s[rs.i];
+       rs.j += si;
+       sj = rs.s[rs.j];
+       rs.s[rs.i] = sj;
+       rs.s[rs.j] = si;
+       return rs.s[(si + sj) & 0xff];
+}
+
+static struct imap_cmd *
+v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
+                 const char *fmt, va_list ap )
+{
+       imap_t *imap = ctx->imap;
+       struct imap_cmd *cmd;
+       int n, bufl;
+       char buf[1024];
+
+       cmd = xmalloc( sizeof(struct imap_cmd) );
+       nfvasprintf( &cmd->cmd, fmt, ap );
+       cmd->tag = ++imap->nexttag;
+
+       if (cb)
+               cmd->cb = *cb;
+       else
+               memset( &cmd->cb, 0, sizeof(cmd->cb) );
+
+       while (imap->literal_pending)
+               get_cmd_result( ctx, NULL );
+
+       bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
+                          "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
+                          cmd->tag, cmd->cmd, cmd->cb.dlen );
+       if (Verbose) {
+               if (imap->num_in_progress)
+                       printf( "(%d in progress) ", imap->num_in_progress );
+               if (memcmp( cmd->cmd, "LOGIN", 5 ))
+                       printf( ">>> %s", buf );
+               else
+                       printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag );
+       }
+       if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
+               free( cmd->cmd );
+               free( cmd );
+               if (cb && cb->data)
+                       free( cb->data );
+               return NULL;
+       }
+       if (cmd->cb.data) {
+               if (CAP(LITERALPLUS)) {
+                       n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen );
+                       free( cmd->cb.data );
+                       if (n != cmd->cb.dlen ||
+                           (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
+                       {
+                               free( cmd->cmd );
+                               free( cmd );
+                               return NULL;
+                       }
+                       cmd->cb.data = NULL;
+               } else
+                       imap->literal_pending = 1;
+       } else if (cmd->cb.cont)
+               imap->literal_pending = 1;
+       cmd->next = NULL;
+       *imap->in_progress_append = cmd;
+       imap->in_progress_append = &cmd->next;
+       imap->num_in_progress++;
+       return cmd;
+}
+
+static struct imap_cmd *
+issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+       struct imap_cmd *ret;
+       va_list ap;
+
+       va_start( ap, fmt );
+       ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
+       va_end( ap );
+       return ret;
+}
+
+static int
+imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+       va_list ap;
+       struct imap_cmd *cmdp;
+
+       va_start( ap, fmt );
+       cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+       va_end( ap );
+       if (!cmdp)
+               return RESP_BAD;
+
+       return get_cmd_result( ctx, cmdp );
+}
+
+static int
+imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+       va_list ap;
+       struct imap_cmd *cmdp;
+
+       va_start( ap, fmt );
+       cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap );
+       va_end( ap );
+       if (!cmdp)
+               return DRV_STORE_BAD;
+
+       switch (get_cmd_result( ctx, cmdp )) {
+       case RESP_BAD: return DRV_STORE_BAD;
+       case RESP_NO: return DRV_MSG_BAD;
+       default: return DRV_OK;
+       }
+}
+
+static int
+is_atom( list_t *list )
+{
+       return list && list->val && list->val != NIL && list->val != LIST;
+}
+
+static int
+is_list( list_t *list )
+{
+       return list && list->val == LIST;
+}
+
+static void
+free_list( list_t *list )
+{
+       list_t *tmp;
+
+       for (; list; list = tmp) {
+               tmp = list->next;
+               if (is_list( list ))
+                       free_list( list->child );
+               else if (is_atom( list ))
+                       free( list->val );
+               free( list );
+       }
+}
+
+static int
+parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level )
+{
+       list_t *cur;
+       char *s = *sp, *p;
+       int n, bytes;
+
+       for (;;) {
+               while (isspace( (unsigned char)*s ))
+                       s++;
+               if (level && *s == ')') {
+                       s++;
+                       break;
+               }
+               *curp = cur = xmalloc( sizeof(*cur) );
+               curp = &cur->next;
+               cur->val = NULL; /* for clean bail */
+               if (*s == '(') {
+                       /* sublist */
+                       s++;
+                       cur->val = LIST;
+                       if (parse_imap_list_l( imap, &s, &cur->child, level + 1 ))
+                               goto bail;
+               } else if (imap && *s == '{') {
+                       /* literal */
+                       bytes = cur->len = strtol( s + 1, &s, 10 );
+                       if (*s != '}')
+                               goto bail;
+
+                       s = cur->val = xmalloc( cur->len );
+
+                       /* dump whats left over in the input buffer */
+                       n = imap->buf.bytes - imap->buf.offset;
+
+                       if (n > bytes)
+                               /* the entire message fit in the buffer */
+                               n = bytes;
+
+                       memcpy( s, imap->buf.buf + imap->buf.offset, n );
+                       s += n;
+                       bytes -= n;
+
+                       /* mark that we used part of the buffer */
+                       imap->buf.offset += n;
+
+                       /* now read the rest of the message */
+                       while (bytes > 0) {
+                               if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0)
+                                       goto bail;
+                               s += n;
+                               bytes -= n;
+                       }
+
+                       if (buffer_gets( &imap->buf, &s ))
+                               goto bail;
+               } else if (*s == '"') {
+                       /* quoted string */
+                       s++;
+                       p = s;
+                       for (; *s != '"'; s++)
+                               if (!*s)
+                                       goto bail;
+                       cur->len = s - p;
+                       s++;
+                       cur->val = xmalloc( cur->len + 1 );
+                       memcpy( cur->val, p, cur->len );
+                       cur->val[cur->len] = 0;
+               } else {
+                       /* atom */
+                       p = s;
+                       for (; *s && !isspace( (unsigned char)*s ); s++)
+                               if (level && *s == ')')
+                                       break;
+                       cur->len = s - p;
+                       if (cur->len == 3 && !memcmp ("NIL", p, 3))
+                               cur->val = NIL;
+                       else {
+                               cur->val = xmalloc( cur->len + 1 );
+                               memcpy( cur->val, p, cur->len );
+                               cur->val[cur->len] = 0;
+                       }
+               }
+
+               if (!level)
+                       break;
+               if (!*s)
+                       goto bail;
+       }
+       *sp = s;
+       *curp = NULL;
+       return 0;
+
+  bail:
+       *curp = NULL;
+       return -1;
+}
+
+static list_t *
+parse_imap_list( imap_t *imap, char **sp )
+{
+       list_t *head;
+
+       if (!parse_imap_list_l( imap, sp, &head, 0 ))
+               return head;
+       free_list( head );
+       return NULL;
+}
+
+static list_t *
+parse_list( char **sp )
+{
+       return parse_imap_list( NULL, sp );
+}
+
+static void
+parse_capability( imap_t *imap, char *cmd )
+{
+       char *arg;
+       unsigned i;
+
+       imap->caps = 0x80000000;
+       while ((arg = next_arg( &cmd )))
+               for (i = 0; i < ARRAY_SIZE(cap_list); i++)
+                       if (!strcmp( cap_list[i], arg ))
+                               imap->caps |= 1 << i;
+       imap->rcaps = imap->caps;
+}
+
+static int
+parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
+{
+       imap_t *imap = ctx->imap;
+       char *arg, *p;
+
+       if (*s != '[')
+               return RESP_OK;         /* no response code */
+       s++;
+       if (!(p = strchr( s, ']' ))) {
+               fprintf( stderr, "IMAP error: malformed response code\n" );
+               return RESP_BAD;
+       }
+       *p++ = 0;
+       arg = next_arg( &s );
+       if (!strcmp( "UIDVALIDITY", arg )) {
+               if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) {
+                       fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" );
+                       return RESP_BAD;
+               }
+       } else if (!strcmp( "UIDNEXT", arg )) {
+               if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) {
+                       fprintf( stderr, "IMAP error: malformed NEXTUID status\n" );
+                       return RESP_BAD;
+               }
+       } else if (!strcmp( "CAPABILITY", arg )) {
+               parse_capability( imap, s );
+       } else if (!strcmp( "ALERT", arg )) {
+               /* RFC2060 says that these messages MUST be displayed
+                * to the user
+                */
+               for (; isspace( (unsigned char)*p ); p++);
+               fprintf( stderr, "*** IMAP ALERT *** %s\n", p );
+       } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) {
+               if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) ||
+                   !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg )))
+               {
+                       fprintf( stderr, "IMAP error: malformed APPENDUID status\n" );
+                       return RESP_BAD;
+               }
+       }
+       return RESP_OK;
+}
+
+static int
+get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
+{
+       imap_t *imap = ctx->imap;
+       struct imap_cmd *cmdp, **pcmdp, *ncmdp;
+       char *cmd, *arg, *arg1, *p;
+       int n, resp, resp2, tag;
+
+       for (;;) {
+               if (buffer_gets( &imap->buf, &cmd ))
+                       return RESP_BAD;
+
+               arg = next_arg( &cmd );
+               if (*arg == '*') {
+                       arg = next_arg( &cmd );
+                       if (!arg) {
+                               fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+                               return RESP_BAD;
+                       }
+
+                       if (!strcmp( "NAMESPACE", arg )) {
+                               imap->ns_personal = parse_list( &cmd );
+                               imap->ns_other = parse_list( &cmd );
+                               imap->ns_shared = parse_list( &cmd );
+                       } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
+                                  !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
+                               if ((resp = parse_response_code( ctx, NULL, cmd )) != RESP_OK)
+                                       return resp;
+                       } else if (!strcmp( "CAPABILITY", arg ))
+                               parse_capability( imap, cmd );
+                       else if ((arg1 = next_arg( &cmd ))) {
+                               if (!strcmp( "EXISTS", arg1 ))
+                                       ctx->gen.count = atoi( arg );
+                               else if (!strcmp( "RECENT", arg1 ))
+                                       ctx->gen.recent = atoi( arg );
+                       } else {
+                               fprintf( stderr, "IMAP error: unable to parse untagged response\n" );
+                               return RESP_BAD;
+                       }
+               } else if (!imap->in_progress) {
+                       fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" );
+                       return RESP_BAD;
+               } else if (*arg == '+') {
+                       /* This can happen only with the last command underway, as
+                          it enforces a round-trip. */
+                       cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
+                              offsetof(struct imap_cmd, next));
+                       if (cmdp->cb.data) {
+                               n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen );
+                               free( cmdp->cb.data );
+                               cmdp->cb.data = NULL;
+                               if (n != (int)cmdp->cb.dlen)
+                                       return RESP_BAD;
+                       } else if (cmdp->cb.cont) {
+                               if (cmdp->cb.cont( ctx, cmdp, cmd ))
+                                       return RESP_BAD;
+                       } else {
+                               fprintf( stderr, "IMAP error: unexpected command continuation request\n" );
+                               return RESP_BAD;
+                       }
+                       if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2)
+                               return RESP_BAD;
+                       if (!cmdp->cb.cont)
+                               imap->literal_pending = 0;
+                       if (!tcmd)
+                               return DRV_OK;
+               } else {
+                       tag = atoi( arg );
+                       for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
+                               if (cmdp->tag == tag)
+                                       goto gottag;
+                       fprintf( stderr, "IMAP error: unexpected tag %s\n", arg );
+                       return RESP_BAD;
+                 gottag:
+                       if (!(*pcmdp = cmdp->next))
+                               imap->in_progress_append = pcmdp;
+                       imap->num_in_progress--;
+                       if (cmdp->cb.cont || cmdp->cb.data)
+                               imap->literal_pending = 0;
+                       arg = next_arg( &cmd );
+                       if (!strcmp( "OK", arg ))
+                               resp = DRV_OK;
+                       else {
+                               if (!strcmp( "NO", arg )) {
+                                       if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */
+                                               p = strchr( cmdp->cmd, '"' );
+                                               if (!issue_imap_cmd( ctx, NULL, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) {
+                                                       resp = RESP_BAD;
+                                                       goto normal;
+                                               }
+                                               /* not waiting here violates the spec, but a server that does not
+                                                  grok this nonetheless violates it too. */
+                                               cmdp->cb.create = 0;
+                                               if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) {
+                                                       resp = RESP_BAD;
+                                                       goto normal;
+                                               }
+                                               free( cmdp->cmd );
+                                               free( cmdp );
+                                               if (!tcmd)
+                                                       return 0;       /* ignored */
+                                               if (cmdp == tcmd)
+                                                       tcmd = ncmdp;
+                                               continue;
+                                       }
+                                       resp = RESP_NO;
+                               } else /*if (!strcmp( "BAD", arg ))*/
+                                       resp = RESP_BAD;
+                               fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n",
+                                        memcmp (cmdp->cmd, "LOGIN", 5) ?
+                                                       cmdp->cmd : "LOGIN <user> <pass>",
+                                                       arg, cmd ? cmd : "");
+                       }
+                       if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp)
+                               resp = resp2;
+                 normal:
+                       if (cmdp->cb.done)
+                               cmdp->cb.done( ctx, cmdp, resp );
+                       if (cmdp->cb.data)
+                               free( cmdp->cb.data );
+                       free( cmdp->cmd );
+                       free( cmdp );
+                       if (!tcmd || tcmd == cmdp)
+                               return resp;
+               }
+       }
+       /* not reached */
+}
+
+static void
+imap_close_server( imap_store_t *ictx )
+{
+       imap_t *imap = ictx->imap;
+
+       if (imap->buf.sock.fd != -1) {
+               imap_exec( ictx, NULL, "LOGOUT" );
+               close( imap->buf.sock.fd );
+       }
+       free_list( imap->ns_personal );
+       free_list( imap->ns_other );
+       free_list( imap->ns_shared );
+       free( imap );
+}
+
+static void
+imap_close_store( store_t *ctx )
+{
+       imap_close_server( (imap_store_t *)ctx );
+       free_generic_messages( ctx->msgs );
+       free( ctx );
+}
+
+static store_t *
+imap_open_store( imap_server_conf_t *srvc )
+{
+       imap_store_t *ctx;
+       imap_t *imap;
+       char *arg, *rsp;
+       struct hostent *he;
+       struct sockaddr_in addr;
+       int s, a[2], preauth;
+
+       ctx = xcalloc( sizeof(*ctx), 1 );
+
+       ctx->imap = imap = xcalloc( sizeof(*imap), 1 );
+       imap->buf.sock.fd = -1;
+       imap->in_progress_append = &imap->in_progress;
+
+       /* open connection to IMAP server */
+
+       if (srvc->tunnel) {
+               info( "Starting tunnel '%s'... ", srvc->tunnel );
+
+               if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
+                       perror( "socketpair" );
+                       exit( 1 );
+               }
+
+               if (fork() == 0) {
+                       if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1)
+                               _exit( 127 );
+                       close( a[0] );
+                       close( a[1] );
+                       execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL );
+                       _exit( 127 );
+               }
+
+               close (a[0]);
+
+               imap->buf.sock.fd = a[1];
+
+               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 );
+               he = gethostbyname( srvc->host );
+               if (!he) {
+                       perror( "gethostbyname" );
+                       goto bail;
+               }
+               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 ) );
+               if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
+                       close( s );
+                       perror( "connect" );
+                       goto bail;
+               }
+               info( "ok\n" );
+
+               imap->buf.sock.fd = s;
+
+       }
+
+       /* read the greeting string */
+       if (buffer_gets( &imap->buf, &rsp )) {
+               fprintf( stderr, "IMAP error: no greeting response\n" );
+               goto bail;
+       }
+       arg = next_arg( &rsp );
+       if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) {
+               fprintf( stderr, "IMAP error: invalid greeting response\n" );
+               goto bail;
+       }
+       preauth = 0;
+       if (!strcmp( "PREAUTH", arg ))
+               preauth = 1;
+       else if (strcmp( "OK", arg ) != 0) {
+               fprintf( stderr, "IMAP error: unknown greeting response\n" );
+               goto bail;
+       }
+       parse_response_code( ctx, NULL, rsp );
+       if (!imap->caps && imap_exec( ctx, NULL, "CAPABILITY" ) != RESP_OK)
+               goto bail;
+
+       if (!preauth) {
+
+               info ("Logging in...\n");
+               if (!srvc->user) {
+                       fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
+                       goto bail;
+               }
+               if (!srvc->pass) {
+                       char prompt[80];
+                       sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host );
+                       arg = getpass( prompt );
+                       if (!arg) {
+                               perror( "getpass" );
+                               exit( 1 );
+                       }
+                       if (!*arg) {
+                               fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host );
+                               goto bail;
+                       }
+                       /*
+                        * getpass() returns a pointer to a static buffer.  make a copy
+                        * for long term storage.
+                        */
+                       srvc->pass = strdup( arg );
+               }
+               if (CAP(NOLOGIN)) {
+                       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" );
+               if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
+                       fprintf( stderr, "IMAP error: LOGIN failed\n" );
+                       goto bail;
+               }
+       } /* !preauth */
+
+       ctx->prefix = "";
+       ctx->trashnc = 1;
+       return (store_t *)ctx;
+
+  bail:
+       imap_close_store( &ctx->gen );
+       return NULL;
+}
+
+static int
+imap_make_flags( int flags, char *buf )
+{
+       const char *s;
+       unsigned i, d;
+
+       for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
+               if (flags & (1 << i)) {
+                       buf[d++] = ' ';
+                       buf[d++] = '\\';
+                       for (s = Flags[i]; *s; s++)
+                               buf[d++] = *s;
+               }
+       buf[0] = '(';
+       buf[d++] = ')';
+       return d;
+}
+
+#define TUIDL 8
+
+static int
+imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+{
+       imap_store_t *ctx = (imap_store_t *)gctx;
+       imap_t *imap = ctx->imap;
+       struct imap_cmd_cb cb;
+       char *fmap, *buf;
+       const char *prefix, *box;
+       int ret, i, j, d, len, extra, nocr;
+       int start, sbreak = 0, ebreak = 0;
+       char flagstr[128], tuid[TUIDL * 2 + 1];
+
+       memset( &cb, 0, sizeof(cb) );
+
+       fmap = data->data;
+       len = data->len;
+       nocr = !data->crlf;
+       extra = 0, i = 0;
+       if (!CAP(UIDPLUS) && uid) {
+         nloop:
+               start = i;
+               while (i < len)
+                       if (fmap[i++] == '\n') {
+                               extra += nocr;
+                               if (i - 2 + nocr == start) {
+                                       sbreak = ebreak = i - 2 + nocr;
+                                       goto mktid;
+                               }
+                               if (!memcmp( fmap + start, "X-TUID: ", 8 )) {
+                                       extra -= (ebreak = i) - (sbreak = start) + nocr;
+                                       goto mktid;
+                               }
+                               goto nloop;
+                       }
+               /* invalid message */
+               free( fmap );
+               return DRV_MSG_BAD;
+        mktid:
+               for (j = 0; j < TUIDL; j++)
+                       sprintf( tuid + j * 2, "%02x", arc4_getbyte() );
+               extra += 8 + TUIDL * 2 + 2;
+       }
+       if (nocr)
+               for (; i < len; i++)
+                       if (fmap[i] == '\n')
+                               extra++;
+
+       cb.dlen = len + extra;
+       buf = cb.data = xmalloc( cb.dlen );
+       i = 0;
+       if (!CAP(UIDPLUS) && uid) {
+               if (nocr) {
+                       for (; i < sbreak; i++)
+                               if (fmap[i] == '\n') {
+                                       *buf++ = '\r';
+                                       *buf++ = '\n';
+                               } else
+                                       *buf++ = fmap[i];
+               } else {
+                       memcpy( buf, fmap, sbreak );
+                       buf += sbreak;
+               }
+               memcpy( buf, "X-TUID: ", 8 );
+               buf += 8;
+               memcpy( buf, tuid, TUIDL * 2 );
+               buf += TUIDL * 2;
+               *buf++ = '\r';
+               *buf++ = '\n';
+               i = ebreak;
+       }
+       if (nocr) {
+               for (; i < len; i++)
+                       if (fmap[i] == '\n') {
+                               *buf++ = '\r';
+                               *buf++ = '\n';
+                       } else
+                               *buf++ = fmap[i];
+       } else
+               memcpy( buf, fmap + i, len - i );
+
+       free( fmap );
+
+       d = 0;
+       if (data->flags) {
+               d = imap_make_flags( data->flags, flagstr );
+               flagstr[d++] = ' ';
+       }
+       flagstr[d] = 0;
+
+       if (!uid) {
+               box = gctx->conf->trash;
+               prefix = ctx->prefix;
+               cb.create = 1;
+               if (ctx->trashnc)
+                       imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
+       } else {
+               box = gctx->name;
+               prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix;
+               cb.create = 0;
+       }
+       cb.ctx = uid;
+       ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+       imap->caps = imap->rcaps;
+       if (ret != DRV_OK)
+               return ret;
+       if (!uid)
+               ctx->trashnc = 0;
+       else
+               gctx->count++;
+
+       return DRV_OK;
+}
+
+#define CHUNKSIZE 0x1000
+
+static int
+read_message( FILE *f, msg_data_t *msg )
+{
+       int len, r;
+
+       memset( msg, 0, sizeof *msg );
+       len = CHUNKSIZE;
+       msg->data = xmalloc( len+1 );
+       msg->data[0] = 0;
+
+       while(!feof( f )) {
+               if (msg->len >= len) {
+                       void *p;
+                       len += CHUNKSIZE;
+                       p = xrealloc(msg->data, len+1);
+                       if (!p)
+                               break;
+                       msg->data = p;
+               }
+               r = fread( &msg->data[msg->len], 1, len - msg->len, f );
+               if (r <= 0)
+                       break;
+               msg->len += r;
+       }
+       msg->data[msg->len] = 0;
+       return msg->len;
+}
+
+static int
+count_messages( msg_data_t *msg )
+{
+       int count = 0;
+       char *p = msg->data;
+
+       while (1) {
+               if (!strncmp( "From ", p, 5 )) {
+                       count++;
+                       p += 5;
+               }
+               p = strstr( p+5, "\nFrom ");
+               if (!p)
+                       break;
+               p++;
+       }
+       return count;
+}
+
+static int
+split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
+{
+       char *p, *data;
+
+       memset( msg, 0, sizeof *msg );
+       if (*ofs >= all_msgs->len)
+               return 0;
+
+       data = &all_msgs->data[ *ofs ];
+       msg->len = all_msgs->len - *ofs;
+
+       if (msg->len < 5 || strncmp( data, "From ", 5 ))
+               return 0;
+
+       p = strstr( data, "\nFrom " );
+       if (p)
+               msg->len = &p[1] - data;
+
+       msg->data = xmalloc( msg->len + 1 );
+       if (!msg->data)
+               return 0;
+
+       memcpy( msg->data, data, msg->len );
+       msg->data[ msg->len ] = 0;
+
+       *ofs += msg->len;
+       return 1;
+}
+
+static imap_server_conf_t server =
+{
+       NULL,   /* name */
+       NULL,   /* tunnel */
+       NULL,   /* host */
+       0,      /* port */
+       NULL,   /* user */
+       NULL,   /* pass */
+};
+
+static char *imap_folder;
+
+static int
+git_imap_config(const char *key, const char *val)
+{
+       char imap_key[] = "imap.";
+
+       if (strncmp( key, imap_key, sizeof imap_key - 1 ))
+               return 0;
+       key += sizeof imap_key - 1;
+
+       if (!strcmp( "folder", key )) {
+               imap_folder = strdup( val );
+       } else if (!strcmp( "host", key )) {
+               {
+                       if (!strncmp( "imap:", val, 5 ))
+                               val += 5;
+                       if (!server.port)
+                               server.port = 143;
+               }
+               if (!strncmp( "//", val, 2 ))
+                       val += 2;
+               server.host = strdup( val );
+       }
+       else if (!strcmp( "user", key ))
+               server.user = strdup( val );
+       else if (!strcmp( "pass", key ))
+               server.pass = strdup( val );
+       else if (!strcmp( "port", key ))
+               server.port = git_config_int( key, val );
+       else if (!strcmp( "tunnel", key ))
+               server.tunnel = strdup( val );
+       return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+       msg_data_t all_msgs, msg;
+       store_t *ctx = NULL;
+       int uid = 0;
+       int ofs = 0;
+       int r;
+       int total, n = 0;
+
+       /* init the random number generator */
+       arc4_init();
+
+       git_config( git_imap_config );
+
+       if (!imap_folder) {
+               fprintf( stderr, "no imap store specified\n" );
+               return 1;
+       }
+
+       /* read the messages */
+       if (!read_message( stdin, &all_msgs )) {
+               fprintf(stderr,"nothing to send\n");
+               return 1;
+       }
+
+       total = count_messages( &all_msgs );
+       if (!total) {
+               fprintf(stderr,"no messages to send\n");
+               return 1;
+       }
+
+       /* write it to the imap server */
+       ctx = imap_open_store( &server );
+       if (!ctx) {
+               fprintf( stderr,"failed to open store\n");
+               return 1;
+       }
+
+       fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" );
+       ctx->name = imap_folder;
+       while (1) {
+               unsigned percent = n * 100 / total;
+               fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total );
+               if (!split_msg( &all_msgs, &msg, &ofs ))
+                       break;
+               r = imap_store_msg( ctx, &msg, &uid );
+               if (r != DRV_OK) break;
+               n++;
+       }
+       fprintf( stderr,"\n" );
+
+       imap_close_store( ctx );
+
+       return 0;
+}
diff --git a/index-pack.c b/index-pack.c
new file mode 100644 (file)
index 0000000..b39953d
--- /dev/null
@@ -0,0 +1,472 @@
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+
+static const char index_pack_usage[] =
+"git-index-pack [-o index-file] pack-file";
+
+struct object_entry
+{
+       unsigned long offset;
+       enum object_type type;
+       enum object_type real_type;
+       unsigned char sha1[20];
+};
+
+struct delta_entry
+{
+       struct object_entry *obj;
+       unsigned char base_sha1[20];
+};
+
+static const char *pack_name;
+static unsigned char *pack_base;
+static unsigned long pack_size;
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static int nr_objects;
+static int nr_deltas;
+
+static void open_pack_file(void)
+{
+       int fd;
+       struct stat st;
+
+       fd = open(pack_name, O_RDONLY);
+       if (fd < 0)
+               die("cannot open packfile '%s': %s", pack_name,
+                   strerror(errno));
+       if (fstat(fd, &st)) {
+               int err = errno;
+               close(fd);
+               die("cannot fstat packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       pack_size = st.st_size;
+       pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (pack_base == MAP_FAILED) {
+               int err = errno;
+               close(fd);
+               die("cannot mmap packfile '%s': %s", pack_name,
+                   strerror(err));
+       }
+       close(fd);
+}
+
+static void parse_pack_header(void)
+{
+       const struct pack_header *hdr;
+       unsigned char sha1[20];
+       SHA_CTX ctx;
+
+       /* Ensure there are enough bytes for the header and final SHA1 */
+       if (pack_size < sizeof(struct pack_header) + 20)
+               die("packfile '%s' is too small", pack_name);
+
+       /* Header consistency check */
+       hdr = (void *)pack_base;
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               die("packfile '%s' signature mismatch", pack_name);
+       if (!pack_version_ok(hdr->hdr_version))
+               die("packfile '%s' version %d unsupported",
+                   pack_name, ntohl(hdr->hdr_version));
+
+       nr_objects = ntohl(hdr->hdr_entries);
+
+       /* Check packfile integrity */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               die("packfile '%s' SHA1 mismatch", pack_name);
+}
+
+static void bad_object(unsigned long offset, const char *format,
+                      ...) NORETURN __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+       va_list params;
+       char buf[1024];
+
+       va_start(params, format);
+       vsnprintf(buf, sizeof(buf), format, params);
+       va_end(params);
+       die("packfile '%s': bad object at offset %lu: %s",
+           pack_name, offset, buf);
+}
+
+static void *unpack_entry_data(unsigned long offset,
+                              unsigned long *current_pos, unsigned long size)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = *current_pos;
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = pack_base + pos;
+       stream.avail_in = pack_limit - pos;
+       inflateInit(&stream);
+
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               if (ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       bad_object(offset, "inflate returned %d", ret);
+       }
+       inflateEnd(&stream);
+       if (stream.total_out != size)
+               bad_object(offset, "size mismatch (expected %lu, got %lu)",
+                          size, stream.total_out);
+       *current_pos = pack_limit - stream.avail_in;
+       return buf;
+}
+
+static void *unpack_raw_entry(unsigned long offset,
+                             enum object_type *obj_type,
+                             unsigned long *obj_size,
+                             unsigned char *delta_base,
+                             unsigned long *next_obj_offset)
+{
+       unsigned long pack_limit = pack_size - 20;
+       unsigned long pos = offset;
+       unsigned char c;
+       unsigned long size;
+       unsigned shift;
+       enum object_type type;
+       void *data;
+
+       c = pack_base[pos++];
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               if (pos >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               c = pack_base[pos++];
+               size += (c & 0x7fUL) << shift;
+               shift += 7;
+       }
+
+       switch (type) {
+       case OBJ_DELTA:
+               if (pos + 20 >= pack_limit)
+                       bad_object(offset, "object extends past end of pack");
+               memcpy(delta_base, pack_base + pos, 20);
+               pos += 20;
+               /* fallthru */
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               data = unpack_entry_data(offset, &pos, size);
+               break;
+       default:
+               bad_object(offset, "bad object type %d", type);
+       }
+
+       *obj_type = type;
+       *obj_size = size;
+       *next_obj_offset = pos;
+       return data;
+}
+
+static int find_delta(const unsigned char *base_sha1)
+{
+       int first = 0, last = nr_deltas;
+
+        while (first < last) {
+                int next = (first + last) / 2;
+                struct delta_entry *delta = &deltas[next];
+                int cmp;
+
+                cmp = memcmp(base_sha1, delta->base_sha1, 20);
+                if (!cmp)
+                        return next;
+                if (cmp < 0) {
+                        last = next;
+                        continue;
+                }
+                first = next+1;
+        }
+        return -first-1;
+}
+
+static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
+                                    int *first_index, int *last_index)
+{
+       int first = find_delta(base_sha1);
+       int last = first;
+       int end = nr_deltas - 1;
+
+       if (first < 0)
+               return -1;
+       while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20))
+               --first;
+       while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20))
+               ++last;
+       *first_index = first;
+       *last_index = last;
+       return 0;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+                       enum object_type type, unsigned char *sha1)
+{
+       SHA_CTX ctx;
+       char header[50];
+       int header_size;
+       const char *type_str;
+
+       switch (type) {
+       case OBJ_COMMIT: type_str = commit_type; break;
+       case OBJ_TREE:   type_str = tree_type; break;
+       case OBJ_BLOB:   type_str = blob_type; break;
+       case OBJ_TAG:    type_str = tag_type; break;
+       default:
+               die("bad type %d", type);
+       }
+
+       header_size = sprintf(header, "%s %lu", type_str, size) + 1;
+
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, header, header_size);
+       SHA1_Update(&ctx, data, size);
+       SHA1_Final(sha1, &ctx);
+}
+
+static void resolve_delta(struct delta_entry *delta, void *base_data,
+                         unsigned long base_size, enum object_type type)
+{
+       struct object_entry *obj = delta->obj;
+       void *delta_data;
+       unsigned long delta_size;
+       void *result;
+       unsigned long result_size;
+       enum object_type delta_type;
+       unsigned char base_sha1[20];
+       unsigned long next_obj_offset;
+       int j, first, last;
+
+       obj->real_type = type;
+       delta_data = unpack_raw_entry(obj->offset, &delta_type,
+                                     &delta_size, base_sha1,
+                                     &next_obj_offset);
+       result = patch_delta(base_data, base_size, delta_data, delta_size,
+                            &result_size);
+       free(delta_data);
+       if (!result)
+               bad_object(obj->offset, "failed to apply delta");
+       sha1_object(result, result_size, type, obj->sha1);
+       if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], result, result_size, type);
+       }
+       free(result);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+       const struct delta_entry *delta_a = a;
+       const struct delta_entry *delta_b = b;
+       return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20);
+}
+
+static void parse_pack_objects(void)
+{
+       int i;
+       unsigned long offset = sizeof(struct pack_header);
+       unsigned char base_sha1[20];
+       void *data;
+       unsigned long data_size;
+
+       /*
+        * First pass:
+        * - find locations of all objects;
+        * - calculate SHA1 of all non-delta objects;
+        * - remember base SHA1 for all deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               obj->offset = offset;
+               data = unpack_raw_entry(offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               obj->real_type = obj->type;
+               if (obj->type == OBJ_DELTA) {
+                       struct delta_entry *delta = &deltas[nr_deltas++];
+                       delta->obj = obj;
+                       memcpy(delta->base_sha1, base_sha1, 20);
+               } else
+                       sha1_object(data, data_size, obj->type, obj->sha1);
+               free(data);
+       }
+       if (offset != pack_size - 20)
+               die("packfile '%s' has junk at the end", pack_name);
+
+       /* Sort deltas by base SHA1 for fast searching */
+       qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+             compare_delta_entry);
+
+       /*
+        * Second pass:
+        * - for all non-delta objects, look if it is used as a base for
+        *   deltas;
+        * - if used as a base, uncompress the object and apply all deltas,
+        *   recursively checking if the resulting object is used as a base
+        *   for some more deltas.
+        */
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+               int j, first, last;
+
+               if (obj->type == OBJ_DELTA)
+                       continue;
+               if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+                       continue;
+               data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
+                                       base_sha1, &offset);
+               for (j = first; j <= last; j++)
+                       resolve_delta(&deltas[j], data, data_size, obj->type);
+               free(data);
+       }
+
+       /* Check for unresolved deltas */
+       for (i = 0; i < nr_deltas; i++) {
+               if (deltas[i].obj->real_type == OBJ_DELTA)
+                       die("packfile '%s' has unresolved deltas",  pack_name);
+       }
+}
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+       struct object_entry *a = *(struct object_entry **)_a;
+       struct object_entry *b = *(struct object_entry **)_b;
+       return memcmp(a->sha1, b->sha1, 20);
+}
+
+static void write_index_file(const char *index_name, unsigned char *sha1)
+{
+       struct sha1file *f;
+       struct object_entry **sorted_by_sha, **list, **last;
+       unsigned int array[256];
+       int i;
+       SHA_CTX ctx;
+
+       if (nr_objects) {
+               sorted_by_sha =
+                       xcalloc(nr_objects, sizeof(struct object_entry *));
+               list = sorted_by_sha;
+               last = sorted_by_sha + nr_objects;
+               for (i = 0; i < nr_objects; ++i)
+                       sorted_by_sha[i] = &objects[i];
+               qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+                     sha1_compare);
+
+       }
+       else
+               sorted_by_sha = list = last = NULL;
+
+       unlink(index_name);
+       f = sha1create("%s", index_name);
+
+       /*
+        * Write the first-level table (the list is sorted,
+        * but we use a 256-entry lookup to be able to avoid
+        * having to do eight extra binary search iterations).
+        */
+       for (i = 0; i < 256; i++) {
+               struct object_entry **next = list;
+               while (next < last) {
+                       struct object_entry *obj = *next;
+                       if (obj->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * sizeof(int));
+
+       /* recompute the SHA1 hash of sorted object names.
+        * currently pack-objects does not do this, but that
+        * can be fixed.
+        */
+       SHA1_Init(&ctx);
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = *list++;
+               unsigned int offset = htonl(obj->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, obj->sha1, 20);
+               SHA1_Update(&ctx, obj->sha1, 20);
+       }
+       sha1write(f, pack_base + pack_size - 20, 20);
+       sha1close(f, NULL, 1);
+       free(sorted_by_sha);
+       SHA1_Final(sha1, &ctx);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *index_name = NULL;
+       char *index_name_buf = NULL;
+       unsigned char sha1[20];
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-o")) {
+                               if (index_name || (i+1) >= argc)
+                                       usage(index_pack_usage);
+                               index_name = argv[++i];
+                       } else
+                               usage(index_pack_usage);
+                       continue;
+               }
+
+               if (pack_name)
+                       usage(index_pack_usage);
+               pack_name = arg;
+       }
+
+       if (!pack_name)
+               usage(index_pack_usage);
+       if (!index_name) {
+               int len = strlen(pack_name);
+               if (len < 5 || strcmp(pack_name + len - 5, ".pack"))
+                       die("packfile name '%s' does not end with '.pack'",
+                           pack_name);
+               index_name_buf = xmalloc(len);
+               memcpy(index_name_buf, pack_name, len - 5);
+               strcpy(index_name_buf + len - 5, ".idx");
+               index_name = index_name_buf;
+       }
+
+       open_pack_file();
+       parse_pack_header();
+       objects = xcalloc(nr_objects, sizeof(struct object_entry));
+       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+       parse_pack_objects();
+       free(deltas);
+       write_index_file(index_name, sha1);
+       free(objects);
+       free(index_name_buf);
+
+       printf("%s\n", sha1_to_hex(sha1));
+
+       return 0;
+}
diff --git a/local-fetch.c b/local-fetch.c
new file mode 100644 (file)
index 0000000..ffa4887
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "fetch.h"
+
+static int use_link = 0;
+static int use_symlink = 0;
+static int use_filecopy = 1;
+
+static char *path; /* "Remote" git repository */
+
+void prefetch(unsigned char *sha1)
+{
+}
+
+static struct packed_git *packs = NULL;
+
+static void setup_index(unsigned char *sha1)
+{
+       struct packed_git *new_pack;
+       char filename[PATH_MAX];
+       strcpy(filename, path);
+       strcat(filename, "/objects/pack/pack-");
+       strcat(filename, sha1_to_hex(sha1));
+       strcat(filename, ".idx");
+       new_pack = parse_pack_index_file(sha1, filename);
+       new_pack->next = packs;
+       packs = new_pack;
+}
+
+static int setup_indices(void)
+{
+       DIR *dir;
+       struct dirent *de;
+       char filename[PATH_MAX];
+       unsigned char sha1[20];
+       sprintf(filename, "%s/objects/pack/", path);
+       dir = opendir(filename);
+       if (!dir)
+               return -1;
+       while ((de = readdir(dir)) != NULL) {
+               int namelen = strlen(de->d_name);
+               if (namelen != 50 || 
+                   strcmp(de->d_name + namelen - 5, ".pack"))
+                       continue;
+               get_sha1_hex(de->d_name + 5, sha1);
+               setup_index(sha1);
+       }
+       closedir(dir);
+       return 0;
+}
+
+static int copy_file(const char *source, char *dest, const char *hex,
+                    int warn_if_not_exists)
+{
+       safe_create_leading_directories(dest);
+       if (use_link) {
+               if (!link(source, dest)) {
+                       pull_say("link %s\n", hex);
+                       return 0;
+               }
+               /* If we got ENOENT there is no point continuing. */
+               if (errno == ENOENT) {
+                       if (warn_if_not_exists)
+                               fprintf(stderr, "does not exist %s\n", source);
+                       return -1;
+               }
+       }
+       if (use_symlink) {
+               struct stat st;
+               if (stat(source, &st)) {
+                       if (!warn_if_not_exists && errno == ENOENT)
+                               return -1;
+                       fprintf(stderr, "cannot stat %s: %s\n", source,
+                               strerror(errno));
+                       return -1;
+               }
+               if (!symlink(source, dest)) {
+                       pull_say("symlink %s\n", hex);
+                       return 0;
+               }
+       }
+       if (use_filecopy) {
+               int ifd, ofd, status = 0;
+
+               ifd = open(source, O_RDONLY);
+               if (ifd < 0) {
+                       if (!warn_if_not_exists && errno == ENOENT)
+                               return -1;
+                       fprintf(stderr, "cannot open %s\n", source);
+                       return -1;
+               }
+               ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
+               if (ofd < 0) {
+                       fprintf(stderr, "cannot open %s\n", dest);
+                       close(ifd);
+                       return -1;
+               }
+               status = copy_fd(ifd, ofd);
+               close(ofd);
+               if (status)
+                       fprintf(stderr, "cannot write %s\n", dest);
+               else
+                       pull_say("copy %s\n", hex);
+               return status;
+       }
+       fprintf(stderr, "failed to copy %s with given copy methods.\n", hex);
+       return -1;
+}
+
+static int fetch_pack(const unsigned char *sha1)
+{
+       struct packed_git *target;
+       char filename[PATH_MAX];
+       if (setup_indices())
+               return -1;
+       target = find_sha1_pack(sha1, packs);
+       if (!target)
+               return error("Couldn't find %s: not separate or in any pack", 
+                            sha1_to_hex(sha1));
+       if (get_verbosely) {
+               fprintf(stderr, "Getting pack %s\n",
+                       sha1_to_hex(target->sha1));
+               fprintf(stderr, " which contains %s\n",
+                       sha1_to_hex(sha1));
+       }
+       sprintf(filename, "%s/objects/pack/pack-%s.pack", 
+               path, sha1_to_hex(target->sha1));
+       copy_file(filename, sha1_pack_name(target->sha1),
+                 sha1_to_hex(target->sha1), 1);
+       sprintf(filename, "%s/objects/pack/pack-%s.idx", 
+               path, sha1_to_hex(target->sha1));
+       copy_file(filename, sha1_pack_index_name(target->sha1),
+                 sha1_to_hex(target->sha1), 1);
+       install_packed_git(target);
+       return 0;
+}
+
+static int fetch_file(const unsigned char *sha1)
+{
+       static int object_name_start = -1;
+       static char filename[PATH_MAX];
+       char *hex = sha1_to_hex(sha1);
+       char *dest_filename = sha1_file_name(sha1);
+
+       if (object_name_start < 0) {
+               strcpy(filename, path); /* e.g. git.git */
+               strcat(filename, "/objects/");
+               object_name_start = strlen(filename);
+       }
+       filename[object_name_start+0] = hex[0];
+       filename[object_name_start+1] = hex[1];
+       filename[object_name_start+2] = '/';
+       strcpy(filename + object_name_start + 3, hex + 2);
+       return copy_file(filename, dest_filename, hex, 0);
+}
+
+int fetch(unsigned char *sha1)
+{
+       if (has_sha1_file(sha1))
+               return 0;
+       else
+               return fetch_file(sha1) && fetch_pack(sha1);
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+       static int ref_name_start = -1;
+       static char filename[PATH_MAX];
+       static char hex[41];
+       int ifd;
+
+       if (ref_name_start < 0) {
+               sprintf(filename, "%s/refs/", path);
+               ref_name_start = strlen(filename);
+       }
+       strcpy(filename + ref_name_start, ref);
+       ifd = open(filename, O_RDONLY);
+       if (ifd < 0) {
+               close(ifd);
+               fprintf(stderr, "cannot open %s\n", filename);
+               return -1;
+       }
+       if (read(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
+               close(ifd);
+               fprintf(stderr, "cannot read from %s\n", filename);
+               return -1;
+       }
+       close(ifd);
+       pull_say("ref %s\n", sha1_to_hex(sha1));
+       return 0;
+}
+
+static const char local_pull_usage[] =
+"git-local-fetch [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path";
+
+/* 
+ * By default we only use file copy.
+ * If -l is specified, a hard link is attempted.
+ * If -s is specified, then a symlink is attempted.
+ * If -n is _not_ specified, then a regular file-to-file copy is done.
+ */
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       int arg = 1;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't')
+                       get_tree = 1;
+               else if (argv[arg][1] == 'c')
+                       get_history = 1;
+               else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               }
+               else if (argv[arg][1] == 'l')
+                       use_link = 1;
+               else if (argv[arg][1] == 's')
+                       use_symlink = 1;
+               else if (argv[arg][1] == 'n')
+                       use_filecopy = 0;
+               else if (argv[arg][1] == 'v')
+                       get_verbosely = 1;
+               else if (argv[arg][1] == 'w')
+                       write_ref = argv[++arg];
+               else if (!strcmp(argv[arg], "--recover"))
+                       get_recover = 1;
+               else
+                       usage(local_pull_usage);
+               arg++;
+       }
+       if (argc < arg + 2)
+               usage(local_pull_usage);
+       commit_id = argv[arg];
+       path = argv[arg + 1];
+       write_ref_log_details = path;
+
+       if (pull(commit_id))
+               return 1;
+
+       return 0;
+}
diff --git a/lockfile.c b/lockfile.c
new file mode 100644 (file)
index 0000000..9bc6083
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2005, Junio C Hamano
+ */
+#include <signal.h>
+#include "cache.h"
+
+static struct lock_file *lock_file_list;
+
+static void remove_lock_file(void)
+{
+       while (lock_file_list) {
+               if (lock_file_list->filename[0])
+                       unlink(lock_file_list->filename);
+               lock_file_list = lock_file_list->next;
+       }
+}
+
+static void remove_lock_file_on_signal(int signo)
+{
+       remove_lock_file();
+       signal(SIGINT, SIG_DFL);
+       raise(signo);
+}
+
+int hold_lock_file_for_update(struct lock_file *lk, const char *path)
+{
+       int fd;
+       sprintf(lk->filename, "%s.lock", path);
+       fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
+       if (fd >=0 && !lk->next) {
+               lk->next = lock_file_list;
+               lock_file_list = lk;
+               signal(SIGINT, remove_lock_file_on_signal);
+               atexit(remove_lock_file);
+       }
+       return fd;
+}
+
+int commit_lock_file(struct lock_file *lk)
+{
+       char result_file[PATH_MAX];
+       int i;
+       strcpy(result_file, lk->filename);
+       i = strlen(result_file) - 5; /* .lock */
+       result_file[i] = 0;
+       i = rename(lk->filename, result_file);
+       lk->filename[0] = 0;
+       return i;
+}
+
+void rollback_lock_file(struct lock_file *lk)
+{
+       if (lk->filename[0])
+               unlink(lk->filename);
+       lk->filename[0] = 0;
+}
+
diff --git a/log-tree.c b/log-tree.c
new file mode 100644 (file)
index 0000000..ebb49f2
--- /dev/null
@@ -0,0 +1,269 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+#include "log-tree.h"
+
+static void show_parents(struct commit *commit, int abbrev)
+{
+       struct commit_list *p;
+       for (p = commit->parents; p ; p = p->next) {
+               struct commit *parent = p->item;
+               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+       }
+}
+
+static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+{
+       int signoff_len = strlen(signoff);
+       static const char signed_off_by[] = "Signed-off-by: ";
+       char *cp = buf;
+
+       /* Do we have enough space to add it? */
+       if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2)
+               return at;
+
+       /* First see if we already have the sign-off by the signer */
+       while (1) {
+               cp = strstr(cp, signed_off_by);
+               if (!cp)
+                       break;
+               cp += strlen(signed_off_by);
+               if ((cp + signoff_len < buf + at) &&
+                   !strncmp(cp, signoff, signoff_len) &&
+                   isspace(cp[signoff_len]))
+                       return at; /* we already have him */
+       }
+
+       strcpy(buf + at, signed_off_by);
+       at += strlen(signed_off_by);
+       strcpy(buf + at, signoff);
+       at += signoff_len;
+       buf[at++] = '\n';
+       buf[at] = 0;
+       return at;
+}
+
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
+{
+       static char this_header[16384];
+       struct commit *commit = log->commit, *parent = log->parent;
+       int abbrev = opt->diffopt.abbrev;
+       int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
+       const char *extra;
+       int len;
+       const char *subject = NULL, *extra_headers = opt->extra_headers;
+
+       opt->loginfo = NULL;
+       if (!opt->verbose_header) {
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               putchar('\n');
+               return;
+       }
+
+       /*
+        * The "oneline" format has several special cases:
+        *  - The pretty-printed commit lacks a newline at the end
+        *    of the buffer, but we do want to make sure that we
+        *    have a newline there. If the separator isn't already
+        *    a newline, add an extra one.
+        *  - unlike other log messages, the one-line format does
+        *    not have an empty line between entries.
+        */
+       extra = "";
+       if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
+               extra = "\n";
+       if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
+               putchar('\n');
+       opt->shown_one = 1;
+
+       /*
+        * Print header line of header..
+        */
+
+       if (opt->commit_format == CMIT_FMT_EMAIL) {
+               char *sha1 = sha1_to_hex(commit->object.sha1);
+               if (opt->total > 0) {
+                       static char buffer[64];
+                       snprintf(buffer, sizeof(buffer),
+                                       "Subject: [PATCH %d/%d] ",
+                                       opt->nr, opt->total);
+                       subject = buffer;
+               } else if (opt->total == 0)
+                       subject = "Subject: [PATCH] ";
+               else
+                       subject = "Subject: ";
+
+               printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
+               if (opt->mime_boundary) {
+                       static char subject_buffer[1024];
+                       static char buffer[1024];
+                       snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+                                "%s"
+                                "MIME-Version: 1.0\n"
+                                "Content-Type: multipart/mixed;\n"
+                                " boundary=\"%s%s\"\n"
+                                "\n"
+                                "This is a multi-part message in MIME "
+                                "format.\n"
+                                "--%s%s\n"
+                                "Content-Type: text/plain; "
+                                "charset=UTF-8; format=fixed\n"
+                                "Content-Transfer-Encoding: 8bit\n\n",
+                                extra_headers ? extra_headers : "",
+                                mime_boundary_leader, opt->mime_boundary,
+                                mime_boundary_leader, opt->mime_boundary);
+                       extra_headers = subject_buffer;
+
+                       snprintf(buffer, sizeof(buffer) - 1,
+                                "--%s%s\n"
+                                "Content-Type: text/x-patch;\n"
+                                " name=\"%s.diff\"\n"
+                                "Content-Transfer-Encoding: 8bit\n"
+                                "Content-Disposition: inline;\n"
+                                " filename=\"%s.diff\"\n\n",
+                                mime_boundary_leader, opt->mime_boundary,
+                                sha1, sha1);
+                       opt->diffopt.stat_sep = buffer;
+               }
+       } else {
+               printf("%s%s",
+                      opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+                      diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               if (parent)
+                       printf(" (from %s)",
+                              diff_unique_abbrev(parent->object.sha1,
+                                                 abbrev_commit));
+               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+       }
+
+       /*
+        * And then the pretty-printed message itself
+        */
+       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev, subject, extra_headers);
+
+       if (opt->add_signoff)
+               len = append_signoff(this_header, sizeof(this_header), len,
+                                    opt->add_signoff);
+       printf("%s%s%s", this_header, extra, sep);
+}
+
+int log_tree_diff_flush(struct rev_info *opt)
+{
+       diffcore_std(&opt->diffopt);
+
+       if (diff_queue_is_empty()) {
+               int saved_fmt = opt->diffopt.output_format;
+               opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
+               diff_flush(&opt->diffopt);
+               opt->diffopt.output_format = saved_fmt;
+               return 0;
+       }
+
+       if (opt->loginfo && !opt->no_commit_id)
+               show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n");
+       diff_flush(&opt->diffopt);
+       return 1;
+}
+
+static int diff_root_tree(struct rev_info *opt,
+                         const unsigned char *new, const char *base)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       tree = read_object_with_reference(new, tree_type, &real.size, NULL);
+       if (!tree)
+               die("unable to read root tree (%s)", sha1_to_hex(new));
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+       retval = diff_tree(&empty, &real, base, &opt->diffopt);
+       free(tree);
+       log_tree_diff_flush(opt);
+       return retval;
+}
+
+static int do_diff_combined(struct rev_info *opt, struct commit *commit)
+{
+       unsigned const char *sha1 = commit->object.sha1;
+
+       diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+       return !opt->loginfo;
+}
+
+/*
+ * Show the diff of a commit.
+ *
+ * Return true if we printed any log info messages
+ */
+static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
+{
+       int showed_log;
+       struct commit_list *parents;
+       unsigned const char *sha1 = commit->object.sha1;
+
+       if (!opt->diff)
+               return 0;
+
+       /* Root commit? */
+       parents = commit->parents;
+       if (!parents) {
+               if (opt->show_root_diff)
+                       diff_root_tree(opt, sha1, "");
+               return !opt->loginfo;
+       }
+
+       /* More than one parent? */
+       if (parents && parents->next) {
+               if (opt->ignore_merges)
+                       return 0;
+               else if (opt->combine_merges)
+                       return do_diff_combined(opt, commit);
+
+               /* If we show individual diffs, show the parent info */
+               log->parent = parents->item;
+       }
+
+       showed_log = 0;
+       for (;;) {
+               struct commit *parent = parents->item;
+
+               diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+               log_tree_diff_flush(opt);
+
+               showed_log |= !opt->loginfo;
+
+               /* Set up the log info for the next parent, if any.. */
+               parents = parents->next;
+               if (!parents)
+                       break;
+               log->parent = parents->item;
+               opt->loginfo = log;
+       }
+       return showed_log;
+}
+
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
+{
+       struct log_info log;
+       int shown;
+
+       log.commit = commit;
+       log.parent = NULL;
+       opt->loginfo = &log;
+
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
+               log.parent = NULL;
+               show_log(opt, opt->loginfo, "");
+               shown = 1;
+       }
+       opt->loginfo = NULL;
+       return shown;
+}
diff --git a/log-tree.h b/log-tree.h
new file mode 100644 (file)
index 0000000..a26e484
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef LOG_TREE_H
+#define LOG_TREE_H
+
+#include "revision.h"
+
+struct log_info {
+       struct commit *commit, *parent;
+};
+
+void init_log_tree_opt(struct rev_info *);
+int log_tree_diff_flush(struct rev_info *);
+int log_tree_commit(struct rev_info *, struct commit *);
+int log_tree_opt_parse(struct rev_info *, const char **, int);
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep);
+
+#endif
diff --git a/mailinfo.c b/mailinfo.c
new file mode 100644 (file)
index 0000000..5b6c215
--- /dev/null
@@ -0,0 +1,823 @@
+/*
+ * 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"
+
+static FILE *cmitmsg, *patchfile;
+
+static int keep_subject = 0;
+static char *metainfo_charset = NULL;
+static char line[1000];
+static char date[1000];
+static char name[1000];
+static char email[1000];
+static char subject[1000];
+
+static enum  {
+       TE_DONTCARE, TE_QP, TE_BASE64,
+} transfer_encoding;
+static char charset[256];
+
+static char multipart_boundary[1000];
+static int multipart_boundary_len;
+static int patch_lines = 0;
+
+static char *sanity_check(char *name, char *email)
+{
+       int len = strlen(name);
+       if (len < 3 || len > 60)
+               return email;
+       if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
+               return email;
+       return name;
+}
+
+static int bogus_from(char *line)
+{
+       /* John Doe <johndoe> */
+       char *bra, *ket, *dst, *cp;
+
+       /* This is fallback, so do not bother if we already have an
+        * e-mail address.
+        */ 
+       if (*email)
+               return 0;
+
+       bra = strchr(line, '<');
+       if (!bra)
+               return 0;
+       ket = strchr(bra, '>');
+       if (!ket)
+               return 0;
+
+       for (dst = email, cp = bra+1; cp < ket; )
+               *dst++ = *cp++;
+       *dst = 0;
+       for (cp = line; isspace(*cp); cp++)
+               ;
+       for (bra--; isspace(*bra); bra--)
+               *bra = 0;
+       cp = sanity_check(cp, email);
+       strcpy(name, cp);
+       return 1;
+}
+
+static int handle_from(char *in_line)
+{
+       char line[1000];
+       char *at;
+       char *dst;
+
+       strcpy(line, in_line);
+       at = strchr(line, '@');
+       if (!at)
+               return bogus_from(line);
+
+       /*
+        * If we already have one email, don't take any confusing lines
+        */
+       if (*email && strchr(at+1, '@'))
+               return 0;
+
+       /* Pick up the string around '@', possibly delimited with <>
+        * pair; that is the email part.  White them out while copying.
+        */
+       while (at > line) {
+               char c = at[-1];
+               if (isspace(c))
+                       break;
+               if (c == '<') {
+                       at[-1] = ' ';
+                       break;
+               }
+               at--;
+       }
+       dst = email;
+       for (;;) {
+               unsigned char c = *at;
+               if (!c || c == '>' || isspace(c)) {
+                       if (c == '>')
+                               *at = ' ';
+                       break;
+               }
+               *at++ = ' ';
+               *dst++ = c;
+       }
+       *dst++ = 0;
+
+       /* The remainder is name.  It could be "John Doe <john.doe@xz>"
+        * or "john.doe@xz (John Doe)", but we have whited out the
+        * email part, so trim from both ends, possibly removing
+        * the () pair at the end.
+        */
+       at = line + strlen(line);
+       while (at > line) {
+               unsigned char c = *--at;
+               if (!isspace(c)) {
+                       at[(c == ')') ? 0 : 1] = 0;
+                       break;
+               }
+       }
+
+       at = line;
+       for (;;) {
+               unsigned char c = *at;
+               if (!c || !isspace(c)) {
+                       if (c == '(')
+                               at++;
+                       break;
+               }
+               at++;
+       }
+       at = sanity_check(at, email);
+       strcpy(name, at);
+       return 1;
+}
+
+static int handle_date(char *line)
+{
+       strcpy(date, line);
+       return 0;
+}
+
+static int handle_subject(char *line)
+{
+       strcpy(subject, line);
+       return 0;
+}
+
+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists.  For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, char *attr)
+{
+       char *ends, *ap = strcasestr(line, name);
+       size_t sz;
+
+       if (!ap) {
+               *attr = 0;
+               return 0;
+       }
+       ap += strlen(name);
+       if (*ap == '"') {
+               ap++;
+               ends = "\"";
+       }
+       else
+               ends = "; \t";
+       sz = strcspn(ap, ends);
+       memcpy(attr, ap, sz);
+       attr[sz] = 0;
+       return 1;
+}
+
+static int handle_subcontent_type(char *line)
+{
+       /* We do not want to mess with boundary.  Note that we do not
+        * handle nested multipart.
+        */
+       if (strcasestr(line, "boundary=")) {
+               fprintf(stderr, "Not handling nested multipart message.\n");
+               exit(1);
+       }
+       slurp_attr(line, "charset=", charset);
+       if (*charset) {
+               int i, c;
+               for (i = 0; (c = charset[i]) != 0; i++)
+                       charset[i] = tolower(c);
+       }
+       return 0;
+}
+
+static int handle_content_type(char *line)
+{
+       *multipart_boundary = 0;
+       if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
+               memcpy(multipart_boundary, "--", 2);
+               multipart_boundary_len = strlen(multipart_boundary);
+       }
+       slurp_attr(line, "charset=", charset);
+       return 0;
+}
+
+static int handle_content_transfer_encoding(char *line)
+{
+       if (strcasestr(line, "base64"))
+               transfer_encoding = TE_BASE64;
+       else if (strcasestr(line, "quoted-printable"))
+               transfer_encoding = TE_QP;
+       else
+               transfer_encoding = TE_DONTCARE;
+       return 0;
+}
+
+static int is_multipart_boundary(const char *line)
+{
+       return (!memcmp(line, multipart_boundary, multipart_boundary_len));
+}
+
+static int eatspace(char *line)
+{
+       int len = strlen(line);
+       while (len > 0 && isspace(line[len-1]))
+               line[--len] = 0;
+       return len;
+}
+
+#define SEEN_FROM 01
+#define SEEN_DATE 02
+#define SEEN_SUBJECT 04
+#define SEEN_BOGUS_UNIX_FROM 010
+#define SEEN_PREFIX  020
+
+/* First lines of body can have From:, Date:, and Subject: */
+static void handle_inbody_header(int *seen, char *line)
+{
+       if (!memcmp(">From", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
+                       *seen |= SEEN_BOGUS_UNIX_FROM;
+                       return;
+               }
+       }
+       if (!memcmp("From:", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
+                       *seen |= SEEN_FROM;
+                       return;
+               }
+       }
+       if (!memcmp("Date:", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_DATE)) {
+                       handle_date(line+6);
+                       *seen |= SEEN_DATE;
+                       return;
+               }
+       }
+       if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
+               if (!(*seen & SEEN_SUBJECT)) {
+                       handle_subject(line+9);
+                       *seen |= SEEN_SUBJECT;
+                       return;
+               }
+       }
+       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+               if (!(*seen & SEEN_SUBJECT)) {
+                       handle_subject(line);
+                       *seen |= SEEN_SUBJECT;
+                       return;
+               }
+       }
+       *seen |= SEEN_PREFIX;
+}
+
+static char *cleanup_subject(char *subject)
+{
+       if (keep_subject)
+               return subject;
+       for (;;) {
+               char *p;
+               int len, remove;
+               switch (*subject) {
+               case 'r': case 'R':
+                       if (!memcmp("e:", subject+1, 2)) {
+                               subject +=3;
+                               continue;
+                       }
+                       break;
+               case ' ': case '\t': case ':':
+                       subject++;
+                       continue;
+
+               case '[':
+                       p = strchr(subject, ']');
+                       if (!p) {
+                               subject++;
+                               continue;
+                       }
+                       len = strlen(p);
+                       remove = p - subject;
+                       if (remove <= len *2) {
+                               subject = p+1;
+                               continue;
+                       }       
+                       break;
+               }
+               return subject;
+       }
+}                      
+
+static void cleanup_space(char *buf)
+{
+       unsigned char c;
+       while ((c = *buf) != 0) {
+               buf++;
+               if (isspace(c)) {
+                       buf[-1] = ' ';
+                       c = *buf;
+                       while (isspace(c)) {
+                               int len = strlen(buf);
+                               memmove(buf, buf+1, len);
+                               c = *buf;
+                       }
+               }
+       }
+}
+
+static void decode_header_bq(char *it);
+typedef int (*header_fn_t)(char *);
+struct header_def {
+       const char *name;
+       header_fn_t func;
+       int namelen;
+};
+
+static void check_header(char *line, struct header_def *header)
+{
+       int i;
+
+       if (header[0].namelen <= 0) {
+               for (i = 0; header[i].name; i++)
+                       header[i].namelen = strlen(header[i].name);
+       }
+       for (i = 0; header[i].name; i++) {
+               int len = header[i].namelen;
+               if (!strncasecmp(line, header[i].name, len) &&
+                   line[len] == ':' && isspace(line[len + 1])) {
+                       /* Unwrap inline B and Q encoding, and optionally
+                        * normalize the meta information to utf8.
+                        */
+                       decode_header_bq(line + len + 2);
+                       header[i].func(line + len + 2);
+                       break;
+               }
+       }
+}
+
+static void check_subheader_line(char *line)
+{
+       static struct header_def header[] = {
+               { "Content-Type", handle_subcontent_type },
+               { "Content-Transfer-Encoding",
+                 handle_content_transfer_encoding },
+               { NULL },
+       };
+       check_header(line, header);
+}
+static void check_header_line(char *line)
+{
+       static struct header_def header[] = {
+               { "From", handle_from },
+               { "Date", handle_date },
+               { "Subject", handle_subject },
+               { "Content-Type", handle_content_type },
+               { "Content-Transfer-Encoding",
+                 handle_content_transfer_encoding },
+               { NULL },
+       };
+       check_header(line, header);
+}
+
+static int is_rfc2822_header(char *line)
+{
+       /*
+        * The section that defines the loosest possible
+        * field name is "3.6.8 Optional fields".
+        *
+        * optional-field = field-name ":" unstructured CRLF
+        * field-name = 1*ftext
+        * ftext = %d33-57 / %59-126
+        */
+       int ch;
+       char *cp = line;
+       while ((ch = *cp++)) {
+               if (ch == ':')
+                       return cp != line;
+               if ((33 <= ch && ch <= 57) ||
+                   (59 <= ch && ch <= 126))
+                       continue;
+               break;
+       }
+       return 0;
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+       int ofs = 0;
+       while (ofs < sz) {
+               int peek, len;
+               if (fgets(line + ofs, sz - ofs, in) == NULL)
+                       break;
+               len = eatspace(line + ofs);
+               if (len == 0)
+                       break;
+               if (!is_rfc2822_header(line)) {
+                       /* Re-add the newline */
+                       line[ofs + len] = '\n';
+                       line[ofs + len + 1] = '\0';
+                       break;
+               }
+               ofs += len;
+               /* Yuck, 2822 header "folding" */
+               peek = fgetc(in); ungetc(peek, in);
+               if (peek != ' ' && peek != '\t')
+                       break;
+       }
+       /* Count mbox From headers as headers */
+       if (!ofs && !memcmp(line, "From ", 5))
+               ofs = 1;
+       return ofs;
+}
+
+static unsigned hexval(int c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       return ~0;
+}
+
+static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
+{
+       int c;
+       while ((c = *in++) != 0 && (in <= ep)) {
+               if (c == '=') {
+                       int d = *in++;
+                       if (d == '\n' || !d)
+                               break; /* drop trailing newline */
+                       *ot++ = ((hexval(d) << 4) | hexval(*in++));
+                       continue;
+               }
+               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+                       c = 0x20;
+               *ot++ = c;
+       }
+       *ot = 0;
+       return 0;
+}
+
+static int decode_b_segment(char *in, char *ot, char *ep)
+{
+       /* Decode in..ep, possibly in-place to ot */
+       int c, pos = 0, acc = 0;
+
+       while ((c = *in++) != 0 && (in <= ep)) {
+               if (c == '+')
+                       c = 62;
+               else if (c == '/')
+                       c = 63;
+               else if ('A' <= c && c <= 'Z')
+                       c -= 'A';
+               else if ('a' <= c && c <= 'z')
+                       c -= 'a' - 26;
+               else if ('0' <= c && c <= '9')
+                       c -= '0' - 52;
+               else if (c == '=') {
+                       /* padding is almost like (c == 0), except we do
+                        * not output NUL resulting only from it;
+                        * for now we just trust the data.
+                        */
+                       c = 0;
+               }
+               else
+                       continue; /* garbage */
+               switch (pos++) {
+               case 0:
+                       acc = (c << 2);
+                       break;
+               case 1:
+                       *ot++ = (acc | (c >> 4));
+                       acc = (c & 15) << 4;
+                       break;
+               case 2:
+                       *ot++ = (acc | (c >> 2));
+                       acc = (c & 3) << 6;
+                       break;
+               case 3:
+                       *ot++ = (acc | c);
+                       acc = pos = 0;
+                       break;
+               }
+       }
+       *ot = 0;
+       return 0;
+}
+
+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
+}
+
+static void decode_header_bq(char *it)
+{
+       char *in, *out, *ep, *cp, *sp;
+       char outbuf[1000];
+
+       in = it;
+       out = outbuf;
+       while ((ep = strstr(in, "=?")) != NULL) {
+               int sz, encoding;
+               char charset_q[256], piecebuf[256];
+               if (in != ep) {
+                       sz = ep - in;
+                       memcpy(out, in, sz);
+                       out += sz;
+                       in += sz;
+               }
+               /* E.g.
+                * ep : "=?iso-2022-jp?B?GyR...?= foo"
+                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+                */
+               ep += 2;
+               cp = strchr(ep, '?');
+               if (!cp)
+                       return; /* no munging */
+               for (sp = ep; sp < cp; sp++)
+                       charset_q[sp - ep] = tolower(*sp);
+               charset_q[cp - ep] = 0;
+               encoding = cp[1];
+               if (!encoding || cp[2] != '?')
+                       return; /* no munging */
+               ep = strstr(cp + 3, "?=");
+               if (!ep)
+                       return; /* no munging */
+               switch (tolower(encoding)) {
+               default:
+                       return; /* no munging */
+               case 'b':
+                       sz = decode_b_segment(cp + 3, piecebuf, ep);
+                       break;
+               case 'q':
+                       sz = decode_q_segment(cp + 3, piecebuf, ep, 1);
+                       break;
+               }
+               if (sz < 0)
+                       return;
+               if (metainfo_charset)
+                       convert_to_utf8(piecebuf, charset_q);
+               strcpy(out, piecebuf);
+               out += strlen(out);
+               in = ep + 2;
+       }
+       strcpy(out, in);
+       strcpy(it, outbuf);
+}
+
+static void decode_transfer_encoding(char *line)
+{
+       char *ep;
+
+       switch (transfer_encoding) {
+       case TE_QP:
+               ep = line + strlen(line);
+               decode_q_segment(line, line, ep, 0);
+               break;
+       case TE_BASE64:
+               ep = line + strlen(line);
+               decode_b_segment(line, line, ep);
+               break;
+       case TE_DONTCARE:
+               break;
+       }
+}
+
+static void handle_info(void)
+{
+       char *sub;
+
+       sub = cleanup_subject(subject);
+       cleanup_space(name);
+       cleanup_space(date);
+       cleanup_space(email);
+       cleanup_space(sub);
+
+       printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
+              name, email, sub, date);
+}
+
+/* We are inside message body and have read line[] already.
+ * Spit out the commit log.
+ */
+static int handle_commit_msg(int *seen)
+{
+       if (!cmitmsg)
+               return 0;
+       do {
+               if (!memcmp("diff -", line, 6) ||
+                   !memcmp("---", line, 3) ||
+                   !memcmp("Index: ", line, 7))
+                       break;
+               if ((multipart_boundary[0] && is_multipart_boundary(line))) {
+                       /* We come here when the first part had only
+                        * the commit message without any patch.  We
+                        * pretend we have not seen this line yet, and
+                        * go back to the loop.
+                        */
+                       return 1;
+               }
+
+               /* Unwrap transfer encoding and optionally
+                * normalize the log message to UTF-8.
+                */
+               decode_transfer_encoding(line);
+               if (metainfo_charset)
+                       convert_to_utf8(line, charset);
+
+               handle_inbody_header(seen, line);
+               if (!(*seen & SEEN_PREFIX))
+                       continue;
+
+               fputs(line, cmitmsg);
+       } while (fgets(line, sizeof(line), stdin) != NULL);
+       fclose(cmitmsg);
+       cmitmsg = NULL;
+       return 0;
+}
+
+/* We have done the commit message and have the first
+ * line of the patch in line[].
+ */
+static void handle_patch(void)
+{
+       do {
+               if (multipart_boundary[0] && is_multipart_boundary(line))
+                       break;
+               /* Only unwrap transfer encoding but otherwise do not
+                * do anything.  We do *NOT* want UTF-8 conversion
+                * here; we are dealing with the user payload.
+                */
+               decode_transfer_encoding(line);
+               fputs(line, patchfile);
+               patch_lines++;
+       } while (fgets(line, sizeof(line), stdin) != NULL);
+}
+
+/* multipart boundary and transfer encoding are set up for us, and we
+ * are at the end of the sub header.  do equivalent of handle_body up
+ * to the next boundary without closing patchfile --- we will expect
+ * that the first part to contain commit message and a patch, and
+ * handle other parts as pure patches.
+ */
+static int handle_multipart_one_part(int *seen)
+{
+       int n = 0;
+
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+       again:
+               n++;
+               if (is_multipart_boundary(line))
+                       break;
+               if (handle_commit_msg(seen))
+                       goto again;
+               handle_patch();
+               break;
+       }
+       if (n == 0)
+               return -1;
+       return 0;
+}
+
+static void handle_multipart_body(void)
+{
+       int seen = 0;
+       int part_num = 0;
+
+       /* Skip up to the first boundary */
+       while (fgets(line, sizeof(line), stdin) != NULL)
+               if (is_multipart_boundary(line)) {
+                       part_num = 1;
+                       break;
+               }
+       if (!part_num)
+               return;
+       /* We are on boundary line.  Start slurping the subhead. */
+       while (1) {
+               int hdr = read_one_header_line(line, sizeof(line), stdin);
+               if (!hdr) {
+                       if (handle_multipart_one_part(&seen) < 0)
+                               return;
+                       /* Reset per part headers */
+                       transfer_encoding = TE_DONTCARE;
+                       charset[0] = 0;
+               }
+               else
+                       check_subheader_line(line);
+       }
+       fclose(patchfile);
+       if (!patch_lines) {
+               fprintf(stderr, "No patch found\n");
+               exit(1);
+       }
+}
+
+/* Non multipart message */
+static void handle_body(void)
+{
+       int seen = 0;
+
+       if (line[0] || fgets(line, sizeof(line), stdin) != NULL) {
+               handle_commit_msg(&seen);
+               handle_patch();
+       }
+       fclose(patchfile);
+       if (!patch_lines) {
+               fprintf(stderr, "No patch found\n");
+               exit(1);
+       }
+}
+
+static const char mailinfo_usage[] =
+       "git-mailinfo [-k] [-u | --encoding=<encoding>] msg patch <mail >info";
+
+int main(int argc, char **argv)
+{
+       /* NEEDSWORK: might want to do the optional .git/ directory
+        * discovery
+        */
+       git_config(git_default_config);
+
+       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;
+               else if (!strncmp(argv[1], "--encoding=", 11))
+                       metainfo_charset = argv[1] + 11;
+               else
+                       usage(mailinfo_usage);
+               argc--; argv++;
+       }
+
+       if (argc != 3)
+               usage(mailinfo_usage);
+       cmitmsg = fopen(argv[1], "w");
+       if (!cmitmsg) {
+               perror(argv[1]);
+               exit(1);
+       }
+       patchfile = fopen(argv[2], "w");
+       if (!patchfile) {
+               perror(argv[2]);
+               exit(1);
+       }
+       while (1) {
+               int hdr = read_one_header_line(line, sizeof(line), stdin);
+               if (!hdr) {
+                       if (multipart_boundary[0])
+                               handle_multipart_body();
+                       else
+                               handle_body();
+                       handle_info();
+                       break;
+               }
+               check_header_line(line);
+       }
+       return 0;
+}
diff --git a/mailsplit.c b/mailsplit.c
new file mode 100644 (file)
index 0000000..70a569c
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Totally braindamaged mbox splitter program.
+ *
+ * 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"
+
+static const char git_mailsplit_usage[] =
+"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>...";
+
+static int is_from_line(const char *line, int len)
+{
+       const char *colon;
+
+       if (len < 20 || memcmp("From ", line, 5))
+               return 0;
+
+       colon = line + len - 2;
+       line += 5;
+       for (;;) {
+               if (colon < line)
+                       return 0;
+               if (*--colon == ':')
+                       break;
+       }
+
+       if (!isdigit(colon[-4]) ||
+           !isdigit(colon[-2]) ||
+           !isdigit(colon[-1]) ||
+           !isdigit(colon[ 1]) ||
+           !isdigit(colon[ 2]))
+               return 0;
+
+       /* year */
+       if (strtol(colon+3, NULL, 10) <= 90)
+               return 0;
+
+       /* Ok, close enough */
+       return 1;
+}
+
+/* Could be as small as 64, enough to hold a Unix "From " line. */
+static char buf[4096];
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line.  Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name, int allow_bare)
+{
+       FILE *output = NULL;
+       int len = strlen(buf);
+       int fd;
+       int status = 0;
+       int is_bare = !is_from_line(buf, len);
+
+       if (is_bare && !allow_bare)
+               goto corrupt;
+
+       fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       if (fd < 0)
+               die("cannot open output file %s", name);
+       output = fdopen(fd, "w");
+
+       /* Copy it out, while searching for a line that begins with
+        * "From " and having something that looks like a date format.
+        */
+       for (;;) {
+               int is_partial = (buf[len-1] != '\n');
+
+               if (fputs(buf, output) == EOF)
+                       die("cannot write output");
+
+               if (fgets(buf, sizeof(buf), mbox) == NULL) {
+                       if (feof(mbox)) {
+                               status = 1;
+                               break;
+                       }
+                       die("cannot read mbox");
+               }
+               len = strlen(buf);
+               if (!is_partial && !is_bare && is_from_line(buf, len))
+                       break; /* done with one message */
+       }
+       fclose(output);
+       return status;
+
+ corrupt:
+       if (output)
+               fclose(output);
+       unlink(name);
+       fprintf(stderr, "corrupt mailbox\n");
+       exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+       int nr = 0, nr_prec = 4;
+       int allow_bare = 0;
+       const char *dir = NULL;
+       const char **argp;
+       static const char *stdin_only[] = { "-", NULL };
+       char *name;
+
+       for (argp = argv+1; *argp; argp++) {
+               const char *arg = *argp;
+
+               if (arg[0] != '-')
+                       break;
+               /* do flags here */
+               if ( arg[1] == 'd' ) {
+                       nr_prec = strtol(arg+2, NULL, 10);
+                       if (nr_prec < 3 || 10 <= nr_prec)
+                               usage(git_mailsplit_usage);
+                       continue;
+               } else if ( arg[1] == 'f' ) {
+                       nr = strtol(arg+2, NULL, 10);
+               } else if ( arg[1] == 'b' && !arg[2] ) {
+                       allow_bare = 1;
+               } else if ( arg[1] == 'o' && arg[2] ) {
+                       dir = arg+2;
+               } else if ( arg[1] == '-' && !arg[2] ) {
+                       argp++; /* -- marks end of options */
+                       break;
+               } else {
+                       die("unknown option: %s", arg);
+               }
+       }
+
+       if ( !dir ) {
+               /* Backwards compatibility: if no -o specified, accept
+                  <mbox> <dir> or just <dir> */
+               switch (argc - (argp-argv)) {
+               case 1:
+                       dir = argp[0];
+                       argp = stdin_only;
+                       break;
+               case 2:
+                       stdin_only[0] = argp[0];
+                       dir = argp[1];
+                       argp = stdin_only;
+                       break;
+               default:
+                       usage(git_mailsplit_usage);
+               }
+       } else {
+               /* New usage: if no more argument, parse stdin */
+               if ( !*argp )
+                       argp = stdin_only;
+       }
+
+       name = xmalloc(strlen(dir) + 2 + 3 * sizeof(nr));
+
+       while (*argp) {
+               const char *file = *argp++;
+               FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+               int file_done = 0;
+
+               if ( !f )
+                       die ("cannot open mbox %s", file);
+
+               if (fgets(buf, sizeof(buf), f) == NULL) {
+                       if (f == stdin)
+                               break; /* empty stdin is OK */
+                       die("cannot read mbox %s", file);
+               }
+
+               while (!file_done) {
+                       sprintf(name, "%s/%0*d", dir, nr_prec, ++nr);
+                       file_done = split_one(f, name, allow_bare);
+               }
+
+               if (f != stdin)
+                       fclose(f);
+       }
+
+       printf("%d\n", nr);
+       return 0;
+}
diff --git a/merge-base.c b/merge-base.c
new file mode 100644 (file)
index 0000000..4856ca0
--- /dev/null
@@ -0,0 +1,264 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+
+#define PARENT1 1
+#define PARENT2 2
+#define UNINTERESTING 4
+
+static struct commit *interesting(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return commit;
+       }
+       return NULL;
+}
+
+/*
+ * A pathological example of how this thing works.
+ *
+ * Suppose we had this commit graph, where chronologically
+ * the timestamp on the commit are A <= B <= C <= D <= E <= F
+ * and we are trying to figure out the merge base for E and F
+ * commits.
+ *
+ *                  F
+ *                 / \
+ *            E   A   D
+ *             \ /   /  
+ *              B   /
+ *               \ /
+ *                C
+ *
+ * First we push E and F to list to be processed.  E gets bit 1
+ * and F gets bit 2.  The list becomes:
+ *
+ *     list=F(2) E(1), result=empty
+ *
+ * Then we pop F, the newest commit, from the list.  Its flag is 2.
+ * We scan its parents, mark them reachable from the side that F is
+ * reachable from, and push them to the list:
+ *
+ *     list=E(1) D(2) A(2), result=empty
+ *
+ * Next pop E and do the same.
+ *
+ *     list=D(2) B(1) A(2), result=empty
+ *
+ * Next pop D and do the same.
+ *
+ *     list=C(2) B(1) A(2), result=empty
+ *
+ * Next pop C and do the same.
+ *
+ *     list=B(1) A(2), result=empty
+ *
+ * Now it is B's turn.  We mark its parent, C, reachable from B's side,
+ * and push it to the list:
+ *
+ *     list=C(3) A(2), result=empty
+ *
+ * Now pop C and notice it has flags==3.  It is placed on the result list,
+ * and the list now contains:
+ *
+ *     list=A(2), result=C(3)
+ *
+ * We pop A and do the same.
+ * 
+ *     list=B(3), result=C(3)
+ *
+ * Next, we pop B and something very interesting happens.  It has flags==3
+ * so it is also placed on the result list, and its parents are marked
+ * uninteresting, retroactively, and placed back on the list:
+ *
+ *    list=C(7), result=C(7) B(3)
+ * 
+ * Now, list does not have any interesting commit.  So we find the newest
+ * commit from the result list that is not marked uninteresting.  Which is
+ * commit B.
+ *
+ *
+ * Another pathological example how this thing used to fail to mark an
+ * ancestor of a merge base as UNINTERESTING before we introduced the
+ * postprocessing phase (mark_reachable_commits).
+ *
+ *               2
+ *               H
+ *         1    / \
+ *         G   A   \
+ *         |\ /     \ 
+ *         | B       \
+ *         |  \       \
+ *          \  C       F
+ *           \  \     / 
+ *            \  D   /   
+ *             \ |  /
+ *              \| /
+ *               E
+ *
+ *      list                   A B C D E F G H
+ *      G1 H2                  - - - - - - 1 2
+ *      H2 E1 B1               - 1 - - 1 - 1 2
+ *      F2 E1 B1 A2            2 1 - - 1 2 1 2
+ *      E3 B1 A2               2 1 - - 3 2 1 2
+ *      B1 A2                  2 1 - - 3 2 1 2
+ *      C1 A2                  2 1 1 - 3 2 1 2
+ *      D1 A2                  2 1 1 1 3 2 1 2
+ *      A2                     2 1 1 1 3 2 1 2
+ *      B3                     2 3 1 1 3 2 1 2
+ *      C7                     2 3 7 1 3 2 1 2
+ *
+ * At this point, unfortunately, everybody in the list is
+ * uninteresting, so we fail to complete the following two
+ * steps to fully marking uninteresting commits.
+ *
+ *      D7                     2 3 7 7 3 2 1 2
+ *      E7                     2 3 7 7 7 2 1 2
+ *
+ * and we ended up showing E as an interesting merge base.
+ * The postprocessing phase re-injects C and continues traversal
+ * to contaminate D and E.
+ */
+
+static int show_all = 0;
+
+static void mark_reachable_commits(struct commit_list *result,
+                                  struct commit_list *list)
+{
+       struct commit_list *tmp;
+
+       /*
+        * Postprocess to fully contaminate the well.
+        */
+       for (tmp = result; tmp; tmp = tmp->next) {
+               struct commit *c = tmp->item;
+               /* Reinject uninteresting ones to list,
+                * so we can scan their parents.
+                */
+               if (c->object.flags & UNINTERESTING)
+                       commit_list_insert(c, &list);
+       }
+       while (list) {
+               struct commit *c = list->item;
+               struct commit_list *parents;
+
+               tmp = list;
+               list = list->next;
+               free(tmp);
+
+               /* Anything taken out of the list is uninteresting, so
+                * mark all its parents uninteresting.  We do not
+                * parse new ones (we already parsed all the relevant
+                * ones).
+                */
+               parents = c->parents;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parents = parents->next;
+                       if (!(p->object.flags & UNINTERESTING)) {
+                               p->object.flags |= UNINTERESTING;
+                               commit_list_insert(p, &list);
+                       }
+               }
+       }
+}
+
+static int merge_base(struct commit *rev1, struct commit *rev2)
+{
+       struct commit_list *list = NULL;
+       struct commit_list *result = NULL;
+       struct commit_list *tmp = NULL;
+
+       if (rev1 == rev2) {
+               printf("%s\n", sha1_to_hex(rev1->object.sha1));
+               return 0;
+       }
+
+       parse_commit(rev1);
+       parse_commit(rev2);
+
+       rev1->object.flags |= 1;
+       rev2->object.flags |= 2;
+       insert_by_date(rev1, &list);
+       insert_by_date(rev2, &list);
+
+       while (interesting(list)) {
+               struct commit *commit = list->item;
+               struct commit_list *parents;
+               int flags = commit->object.flags & 7;
+
+               tmp = list;
+               list = list->next;
+               free(tmp);
+               if (flags == 3) {
+                       insert_by_date(commit, &result);
+
+                       /* Mark parents of a found merge uninteresting */
+                       flags |= UNINTERESTING;
+               }
+               parents = commit->parents;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parents = parents->next;
+                       if ((p->object.flags & flags) == flags)
+                               continue;
+                       parse_commit(p);
+                       p->object.flags |= flags;
+                       insert_by_date(p, &list);
+               }
+       }
+
+       if (!result)
+               return 1;
+
+       if (result->next && list)
+               mark_reachable_commits(result, list);
+
+       while (result) {
+               struct commit *commit = result->item;
+               result = result->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               printf("%s\n", sha1_to_hex(commit->object.sha1));
+               if (!show_all)
+                       return 0;
+               commit->object.flags |= UNINTERESTING;
+       }
+       return 0;
+}
+
+static const char merge_base_usage[] =
+"git-merge-base [--all] <commit-id> <commit-id>";
+
+int main(int argc, char **argv)
+{
+       struct commit *rev1, *rev2;
+       unsigned char rev1key[20], rev2key[20];
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       while (1 < argc && argv[1][0] == '-') {
+               char *arg = argv[1];
+               if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
+                       show_all = 1;
+               else
+                       usage(merge_base_usage);
+               argc--; argv++;
+       }
+       if (argc != 3)
+               usage(merge_base_usage);
+       if (get_sha1(argv[1], rev1key))
+               die("Not a valid object name %s", argv[1]);
+       if (get_sha1(argv[2], rev2key))
+               die("Not a valid object name %s", argv[2]);
+       rev1 = lookup_commit_reference(rev1key);
+       rev2 = lookup_commit_reference(rev2key);
+       if (!rev1 || !rev2)
+               return 1;
+       return merge_base(rev1, rev2);
+}
diff --git a/merge-index.c b/merge-index.c
new file mode 100644 (file)
index 0000000..024196e
--- /dev/null
@@ -0,0 +1,136 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "cache.h"
+
+static const char *pgm = NULL;
+static const char *arguments[8];
+static int one_shot, quiet;
+static int err;
+
+static void run_program(void)
+{
+       int pid = fork(), status;
+
+       if (pid < 0)
+               die("unable to fork");
+       if (!pid) {
+               execlp(pgm, arguments[0],
+                           arguments[1],
+                           arguments[2],
+                           arguments[3],
+                           arguments[4],
+                           arguments[5],
+                           arguments[6],
+                           arguments[7],
+                           NULL);
+               die("unable to execute '%s'", pgm);
+       }
+       if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) {
+               if (one_shot) {
+                       err++;
+               } else {
+                       if (!quiet)
+                               die("merge program failed");
+                       exit(1);
+               }
+       }
+}
+
+static int merge_entry(int pos, const char *path)
+{
+       int found;
+       
+       if (pos >= active_nr)
+               die("git-merge-index: %s not in the cache", path);
+       arguments[0] = pgm;
+       arguments[1] = "";
+       arguments[2] = "";
+       arguments[3] = "";
+       arguments[4] = path;
+       arguments[5] = "";
+       arguments[6] = "";
+       arguments[7] = "";
+       found = 0;
+       do {
+               static char hexbuf[4][60];
+               static char ownbuf[4][60];
+               struct cache_entry *ce = active_cache[pos];
+               int stage = ce_stage(ce);
+
+               if (strcmp(ce->name, path))
+                       break;
+               found++;
+               strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
+               sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode) & (~S_IFMT));
+               arguments[stage] = hexbuf[stage];
+               arguments[stage + 4] = ownbuf[stage];
+       } while (++pos < active_nr);
+       if (!found)
+               die("git-merge-index: %s not in the cache", path);
+       run_program();
+       return found;
+}
+
+static void merge_file(const char *path)
+{
+       int pos = cache_name_pos(path, strlen(path));
+
+       /*
+        * If it already exists in the cache as stage0, it's
+        * already merged and there is nothing to do.
+        */
+       if (pos < 0)
+               merge_entry(-pos-1, path);
+}
+
+static void merge_all(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               i += merge_entry(i, ce->name)-1;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i, force_file = 0;
+
+       if (argc < 3)
+               usage("git-merge-index [-o] [-q] <merge-program> (-a | <filename>*)");
+
+       setup_git_directory();
+       read_cache();
+
+       i = 1;
+       if (!strcmp(argv[i], "-o")) {
+               one_shot = 1;
+               i++;
+       }
+       if (!strcmp(argv[i], "-q")) {
+               quiet = 1;
+               i++;
+       }
+       pgm = argv[i++];
+       for (; i < argc; i++) {
+               char *arg = argv[i];
+               if (!force_file && *arg == '-') {
+                       if (!strcmp(arg, "--")) {
+                               force_file = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-a")) {
+                               merge_all();
+                               continue;
+                       }
+                       die("git-merge-index: unknown option %s", arg);
+               }
+               merge_file(arg);
+       }
+       if (err && !quiet)
+               die("merge program failed");
+       return err;
+}
diff --git a/merge-tree.c b/merge-tree.c
new file mode 100644 (file)
index 0000000..9dcaab7
--- /dev/null
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "tree-walk.h"
+
+static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>";
+static int resolve_directories = 1;
+
+static void merge_trees(struct tree_desc t[3], const char *base);
+
+/* An empty entry never compares same, not even to another empty entry */
+static int same_entry(struct name_entry *a, struct name_entry *b)
+{
+       return  a->sha1 &&
+               b->sha1 &&
+               !memcmp(a->sha1, b->sha1, 20) &&
+               a->mode == b->mode;
+}
+
+static const char *sha1_to_hex_zero(const unsigned char *sha1)
+{
+       if (sha1)
+               return sha1_to_hex(sha1);
+       return "0000000000000000000000000000000000000000";
+}
+
+static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
+{
+       /* If it's already branch1, don't bother showing it */
+       if (!branch1)
+               return;
+
+       printf("0 %06o->%06o %s->%s %s%s\n",
+               branch1->mode, result->mode,
+               sha1_to_hex_zero(branch1->sha1),
+               sha1_to_hex_zero(result->sha1),
+               base, result->path);
+}
+
+static int unresolved_directory(const char *base, struct name_entry n[3])
+{
+       int baselen;
+       char *newbase;
+       struct name_entry *p;
+       struct tree_desc t[3];
+       void *buf0, *buf1, *buf2;
+
+       if (!resolve_directories)
+               return 0;
+       p = n;
+       if (!p->mode) {
+               p++;
+               if (!p->mode)
+                       p++;
+       }
+       if (!S_ISDIR(p->mode))
+               return 0;
+       baselen = strlen(base);
+       newbase = xmalloc(baselen + p->pathlen + 2);
+       memcpy(newbase, base, baselen);
+       memcpy(newbase + baselen, p->path, p->pathlen);
+       memcpy(newbase + baselen + p->pathlen, "/", 2);
+
+       buf0 = fill_tree_descriptor(t+0, n[0].sha1);
+       buf1 = fill_tree_descriptor(t+1, n[1].sha1);
+       buf2 = fill_tree_descriptor(t+2, n[2].sha1);
+       merge_trees(t, newbase);
+
+       free(buf0);
+       free(buf1);
+       free(buf2);
+       free(newbase);
+       return 1;
+}
+
+static void unresolved(const char *base, struct name_entry n[3])
+{
+       if (unresolved_directory(base, n))
+               return;
+       if (n[0].sha1)
+               printf("1 %06o %s %s%s\n", n[0].mode, sha1_to_hex(n[0].sha1), base, n[0].path);
+       if (n[1].sha1)
+               printf("2 %06o %s %s%s\n", n[1].mode, sha1_to_hex(n[1].sha1), base, n[1].path);
+       if (n[2].sha1)
+               printf("3 %06o %s %s%s\n", n[2].mode, sha1_to_hex(n[2].sha1), base, n[2].path);
+}
+
+/*
+ * Merge two trees together (t[1] and t[2]), using a common base (t[0])
+ * as the origin.
+ *
+ * This walks the (sorted) trees in lock-step, checking every possible
+ * name. Note that directories automatically sort differently from other
+ * files (see "base_name_compare"), so you'll never see file/directory
+ * conflicts, because they won't ever compare the same.
+ *
+ * IOW, if a directory changes to a filename, it will automatically be
+ * seen as the directory going away, and the filename being created.
+ *
+ * Think of this as a three-way diff.
+ *
+ * The output will be either:
+ *  - successful merge
+ *      "0 mode sha1 filename"
+ *    NOTE NOTE NOTE! FIXME! We really really need to walk the index
+ *    in parallel with this too!
+ *
+ *  - conflict:
+ *     "1 mode sha1 filename"
+ *     "2 mode sha1 filename"
+ *     "3 mode sha1 filename"
+ *    where not all of the 1/2/3 lines may exist, of course.
+ *
+ * The successful merge rules are the same as for the three-way merge
+ * in git-read-tree.
+ */
+static void threeway_callback(int n, unsigned long mask, struct name_entry *entry, const char *base)
+{
+       /* Same in both? */
+       if (same_entry(entry+1, entry+2)) {
+               if (entry[0].sha1) {
+                       resolve(base, NULL, entry+1);
+                       return;
+               }
+       }
+
+       if (same_entry(entry+0, entry+1)) {
+               if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) {
+                       resolve(base, entry+1, entry+2);
+                       return;
+               }
+       }
+
+       if (same_entry(entry+0, entry+2)) {
+               if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) {
+                       resolve(base, NULL, entry+1);
+                       return;
+               }
+       }
+
+       unresolved(base, entry);
+}
+
+static void merge_trees(struct tree_desc t[3], const char *base)
+{
+       traverse_trees(3, t, base, threeway_callback);
+}
+
+static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
+{
+       unsigned char sha1[20];
+       void *buf;
+
+       if (get_sha1(rev, sha1))
+               die("unknown rev %s", rev);
+       buf = fill_tree_descriptor(desc, sha1);
+       if (!buf)
+               die("%s is not a tree", rev);
+       return buf;
+}
+
+int main(int argc, char **argv)
+{
+       struct tree_desc t[3];
+       void *buf1, *buf2, *buf3;
+
+       if (argc < 4)
+               usage(merge_tree_usage);
+
+       buf1 = get_tree_descriptor(t+0, argv[1]);
+       buf2 = get_tree_descriptor(t+1, argv[2]);
+       buf3 = get_tree_descriptor(t+2, argv[3]);
+       merge_trees(t, "");
+       free(buf1);
+       free(buf2);
+       free(buf3);
+       return 0;
+}
diff --git a/mktag.c b/mktag.c
new file mode 100644 (file)
index 0000000..f0fe528
--- /dev/null
+++ b/mktag.c
@@ -0,0 +1,137 @@
+#include "cache.h"
+#include "tag.h"
+
+/*
+ * A signature file has a very simple fixed format: three lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>",
+ * followed by some free-form signature that git itself doesn't
+ * care about, but that can be verified with gpg or similar.
+ *
+ * The first three lines are guaranteed to be at least 63 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, and "tag .\n" at 6 bytes is the
+ * shortest single-character-tag line. 
+ *
+ * We also artificially limit the size of the full object to 8kB.
+ * Just because I'm a lazy bastard, and if you can't fit a signature
+ * in that size, you're doing something wrong.
+ */
+
+// Some random size
+#define MAXSIZE (8192)
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(unsigned char *sha1, const char *expected_type)
+{
+       int ret = -1;
+       char type[100];
+       unsigned long size;
+       void *buffer = read_sha1_file(sha1, type, &size);
+
+       if (buffer) {
+               if (!strcmp(type, expected_type))
+                       ret = check_sha1_signature(sha1, buffer, size, type);
+               free(buffer);
+       }
+       return ret;
+}
+
+static int verify_tag(char *buffer, unsigned long size)
+{
+       int typelen;
+       char type[20];
+       unsigned char sha1[20];
+       const char *object, *type_line, *tag_line, *tagger_line;
+
+       if (size < 64)
+               return error("wanna fool me ? you obviously got the size wrong !\n");
+
+       buffer[size] = 0;
+
+       /* Verify object line */
+       object = buffer;
+       if (memcmp(object, "object ", 7))
+               return error("char%d: does not start with \"object \"\n", 0);
+
+       if (get_sha1_hex(object + 7, sha1))
+               return error("char%d: could not get SHA1 hash\n", 7);
+
+       /* Verify type line */
+       type_line = object + 48;
+       if (memcmp(type_line - 1, "\ntype ", 6))
+               return error("char%d: could not find \"\\ntype \"\n", 47);
+
+       /* Verify tag-line */
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line)
+               return error("char%td: could not find next \"\\n\"\n", type_line - buffer);
+       tag_line++;
+       if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+               return error("char%td: no \"tag \" found\n", tag_line - buffer);
+
+       /* Get the actual type */
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= sizeof(type))
+               return error("char%td: type too long\n", type_line+5 - buffer);
+
+       memcpy(type, type_line+5, typelen);
+       type[typelen] = 0;
+
+       /* Verify that the object matches */
+       if (get_sha1_hex(object + 7, sha1))
+               return error("char%d: could not get SHA1 hash but this is really odd since i got it before !\n", 7);
+
+       if (verify_object(sha1, type))
+               return error("char%d: could not verify object %s\n", 7, sha1);
+
+       /* Verify the tag-name: we don't allow control characters or spaces in it */
+       tag_line += 4;
+       for (;;) {
+               unsigned char c = *tag_line++;
+               if (c == '\n')
+                       break;
+               if (c > ' ')
+                       continue;
+               return error("char%td: could not verify tag name\n", tag_line - buffer);
+       }
+
+       /* Verify the tagger line */
+       tagger_line = tag_line;
+
+       if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n'))
+               return error("char%td: could not find \"tagger\"\n", tagger_line - buffer);
+
+       /* The actual stuff afterwards we don't care about.. */
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned long size = 4096;
+       char *buffer = malloc(size);
+       unsigned char result_sha1[20];
+
+       if (argc != 1)
+               usage("cat <signaturefile> | git-mktag");
+
+       setup_git_directory();
+
+       if (read_pipe(0, &buffer, &size)) {
+               free(buffer);
+               die("could not read from stdin");
+       }
+
+       // Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger "
+       if (verify_tag(buffer, size) < 0)
+               die("invalid tag signature file");
+
+       if (write_sha1_file(buffer, size, tag_type, result_sha1) < 0)
+               die("unable to write tag file");
+
+       free(buffer);
+
+       printf("%s\n", sha1_to_hex(result_sha1));
+       return 0;
+}
diff --git a/mktree.c b/mktree.c
new file mode 100644 (file)
index 0000000..ab63cd9
--- /dev/null
+++ b/mktree.c
@@ -0,0 +1,138 @@
+/*
+ * GIT - the stupid content tracker
+ *
+ * Copyright (c) Junio C Hamano, 2006
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+#include "tree.h"
+
+static struct treeent {
+       unsigned mode;
+       unsigned char sha1[20];
+       int len;
+       char name[FLEX_ARRAY];
+} **entries;
+static int alloc, used;
+
+static void append_to_tree(unsigned mode, unsigned char *sha1, char *path)
+{
+       struct treeent *ent;
+       int len = strlen(path);
+       if (strchr(path, '/'))
+               die("path %s contains slash", path);
+
+       if (alloc <= used) {
+               alloc = alloc_nr(used);
+               entries = xrealloc(entries, sizeof(*entries) * alloc);
+       }
+       ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1);
+       ent->mode = mode;
+       ent->len = len;
+       memcpy(ent->sha1, sha1, 20);
+       memcpy(ent->name, path, len+1);
+}
+
+static int ent_compare(const void *a_, const void *b_)
+{
+       struct treeent *a = *(struct treeent **)a_;
+       struct treeent *b = *(struct treeent **)b_;
+       return base_name_compare(a->name, a->len, a->mode,
+                                b->name, b->len, b->mode);
+}
+
+static void write_tree(unsigned char *sha1)
+{
+       char *buffer;
+       unsigned long size, offset;
+       int i;
+
+       qsort(entries, used, sizeof(*entries), ent_compare);
+       size = 100;
+       for (size = i = 0; i < used; i++)
+               size += 32 + entries[i]->len;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       for (i = 0; i < used; i++) {
+               struct treeent *ent = entries[i];
+
+               if (offset + ent->len + 100 < size) {
+                       size = alloc_nr(offset + ent->len + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset, "%o ", ent->mode);
+               offset += sprintf(buffer + offset, "%s", ent->name);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, ent->sha1, 20);
+               offset += 20;
+       }
+       write_sha1_file(buffer, offset, tree_type, sha1);
+}
+
+static const char mktree_usage[] = "mktree [-z]";
+
+int main(int ac, char **av)
+{
+       struct strbuf sb;
+       unsigned char sha1[20];
+       int line_termination = '\n';
+
+       setup_git_directory();
+
+       while ((1 < ac) && av[1][0] == '-') {
+               char *arg = av[1];
+               if (!strcmp("-z", arg))
+                       line_termination = 0;
+               else
+                       usage(mktree_usage);
+               ac--;
+               av++;
+       }
+
+       strbuf_init(&sb);
+       while (1) {
+               int len;
+               char *ptr, *ntr;
+               unsigned mode;
+               char type[20];
+               char *path;
+
+               read_line(&sb, stdin, line_termination);
+               if (sb.eof)
+                       break;
+               len = sb.len;
+               ptr = sb.buf;
+               /* Input is non-recursive ls-tree output format
+                * mode SP type SP sha1 TAB name
+                */
+               mode = strtoul(ptr, &ntr, 8);
+               if (ptr == ntr || !ntr || *ntr != ' ')
+                       die("input format error: %s", sb.buf);
+               ptr = ntr + 1; /* type */
+               ntr = strchr(ptr, ' ');
+               if (!ntr || sb.buf + len <= ntr + 41 ||
+                   ntr[41] != '\t' ||
+                   get_sha1_hex(ntr + 1, sha1))
+                       die("input format error: %s", sb.buf);
+               if (sha1_object_info(sha1, type, NULL))
+                       die("object %s unavailable", sha1_to_hex(sha1));
+               *ntr++ = 0; /* now at the beginning of SHA1 */
+               if (strcmp(ptr, type))
+                       die("object type %s mismatch (%s)", ptr, type);
+               ntr += 41; /* at the beginning of name */
+               if (line_termination && ntr[0] == '"')
+                       path = unquote_c_style(ntr, NULL);
+               else
+                       path = ntr;
+
+               append_to_tree(mode, sha1, path);
+
+               if (path != ntr)
+                       free(path);
+       }
+       write_tree(sha1);
+       puts(sha1_to_hex(sha1));
+       exit(0);
+}
diff --git a/mozilla-sha1/sha1.c b/mozilla-sha1/sha1.c
new file mode 100644 (file)
index 0000000..847531d
--- /dev/null
@@ -0,0 +1,152 @@
+/* 
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is SHA 180-1 Reference Implementation (Compact version)
+ * 
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research.  Portions created by Paul Kocher are 
+ * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ *
+ *     Paul Kocher
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+#include "sha1.h"
+
+static void shaHashBlock(SHA_CTX *ctx);
+
+void SHA1_Init(SHA_CTX *ctx) {
+  int i;
+
+  ctx->lenW = 0;
+  ctx->sizeHi = ctx->sizeLo = 0;
+
+  /* Initialize H with the magic constants (see FIPS180 for constants)
+   */
+  ctx->H[0] = 0x67452301;
+  ctx->H[1] = 0xefcdab89;
+  ctx->H[2] = 0x98badcfe;
+  ctx->H[3] = 0x10325476;
+  ctx->H[4] = 0xc3d2e1f0;
+
+  for (i = 0; i < 80; i++)
+    ctx->W[i] = 0;
+}
+
+
+void SHA1_Update(SHA_CTX *ctx, const void *_dataIn, int len) {
+  const unsigned char *dataIn = _dataIn;
+  int i;
+
+  /* Read the data into W and process blocks as they get full
+   */
+  for (i = 0; i < len; i++) {
+    ctx->W[ctx->lenW / 4] <<= 8;
+    ctx->W[ctx->lenW / 4] |= (unsigned int)dataIn[i];
+    if ((++ctx->lenW) % 64 == 0) {
+      shaHashBlock(ctx);
+      ctx->lenW = 0;
+    }
+    ctx->sizeLo += 8;
+    ctx->sizeHi += (ctx->sizeLo < 8);
+  }
+}
+
+
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx) {
+  unsigned char pad0x80 = 0x80;
+  unsigned char pad0x00 = 0x00;
+  unsigned char padlen[8];
+  int i;
+
+  /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length
+   */
+  padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255);
+  padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255);
+  padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255);
+  padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255);
+  padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255);
+  padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255);
+  padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255);
+  padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255);
+  SHA1_Update(ctx, &pad0x80, 1);
+  while (ctx->lenW != 56)
+    SHA1_Update(ctx, &pad0x00, 1);
+  SHA1_Update(ctx, padlen, 8);
+
+  /* Output hash
+   */
+  for (i = 0; i < 20; i++) {
+    hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24);
+    ctx->H[i / 4] <<= 8;
+  }
+
+  /*
+   *  Re-initialize the context (also zeroizes contents)
+   */
+  SHA1_Init(ctx);
+}
+
+
+#define SHA_ROT(X,n) (((X) << (n)) | ((X) >> (32-(n))))
+
+static void shaHashBlock(SHA_CTX *ctx) {
+  int t;
+  unsigned int A,B,C,D,E,TEMP;
+
+  for (t = 16; t <= 79; t++)
+    ctx->W[t] =
+      SHA_ROT(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1);
+
+  A = ctx->H[0];
+  B = ctx->H[1];
+  C = ctx->H[2];
+  D = ctx->H[3];
+  E = ctx->H[4];
+
+  for (t = 0; t <= 19; t++) {
+    TEMP = SHA_ROT(A,5) + (((C^D)&B)^D)     + E + ctx->W[t] + 0x5a827999;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 20; t <= 39; t++) {
+    TEMP = SHA_ROT(A,5) + (B^C^D)           + E + ctx->W[t] + 0x6ed9eba1;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 40; t <= 59; t++) {
+    TEMP = SHA_ROT(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdc;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+  for (t = 60; t <= 79; t++) {
+    TEMP = SHA_ROT(A,5) + (B^C^D)           + E + ctx->W[t] + 0xca62c1d6;
+    E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+  }
+
+  ctx->H[0] += A;
+  ctx->H[1] += B;
+  ctx->H[2] += C;
+  ctx->H[3] += D;
+  ctx->H[4] += E;
+}
+
diff --git a/mozilla-sha1/sha1.h b/mozilla-sha1/sha1.h
new file mode 100644 (file)
index 0000000..5d82afa
--- /dev/null
@@ -0,0 +1,45 @@
+/* 
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ * 
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ * 
+ * The Original Code is SHA 180-1 Header File
+ * 
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research.  Portions created by Paul Kocher are 
+ * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
+ * Rights Reserved.
+ * 
+ * Contributor(s):
+ *
+ *     Paul Kocher
+ * 
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable 
+ * instead of those above.  If you wish to allow use of your 
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL.  If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+typedef struct {
+  unsigned int H[5];
+  unsigned int W[80];
+  int lenW;
+  unsigned int sizeHi,sizeLo;
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *ctx);
+void SHA1_Update(SHA_CTX *ctx, const void *dataIn, int len);
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx);
diff --git a/name-rev.c b/name-rev.c
new file mode 100644 (file)
index 0000000..bad8a53
--- /dev/null
@@ -0,0 +1,246 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+
+static const char name_rev_usage[] =
+       "git-name-rev [--tags] ( --all | --stdin | commitish [commitish...] )\n";
+
+typedef struct rev_name {
+       const char *tip_name;
+       int merge_traversals;
+       int generation;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+static void name_rev(struct commit *commit,
+               const char *tip_name, int merge_traversals, int generation,
+               int deref)
+{
+       struct rev_name *name = (struct rev_name *)commit->object.util;
+       struct commit_list *parents;
+       int parent_number = 1;
+
+       if (!commit->object.parsed)
+               parse_commit(commit);
+
+       if (commit->date < cutoff)
+               return;
+
+       if (deref) {
+               char *new_name = xmalloc(strlen(tip_name)+3);
+               strcpy(new_name, tip_name);
+               strcat(new_name, "^0");
+               tip_name = new_name;
+
+               if (generation)
+                       die("generation: %d, but deref?", generation);
+       }
+
+       if (name == NULL) {
+               name = xmalloc(sizeof(rev_name));
+               commit->object.util = name;
+               goto copy_data;
+       } else if (name->merge_traversals > merge_traversals ||
+                       (name->merge_traversals == merge_traversals &&
+                        name->generation > generation)) {
+copy_data:
+               name->tip_name = tip_name;
+               name->merge_traversals = merge_traversals;
+               name->generation = generation;
+       } else
+               return;
+
+       for (parents = commit->parents;
+                       parents;
+                       parents = parents->next, parent_number++) {
+               if (parent_number > 1) {
+                       char *new_name = xmalloc(strlen(tip_name)+8);
+
+                       if (generation > 0)
+                               sprintf(new_name, "%s~%d^%d", tip_name,
+                                               generation, parent_number);
+                       else
+                               sprintf(new_name, "%s^%d", tip_name, parent_number);
+
+                       name_rev(parents->item, new_name,
+                               merge_traversals + 1 , 0, 0);
+               } else {
+                       name_rev(parents->item, tip_name, merge_traversals,
+                               generation + 1, 0);
+               }
+       }
+}
+
+static int tags_only = 0;
+
+static int name_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+       int deref = 0;
+
+       if (tags_only && strncmp(path, "refs/tags/", 10))
+               return 0;
+
+       while (o && o->type == tag_type) {
+               struct tag *t = (struct tag *) o;
+               if (!t->tagged)
+                       break; /* broken repository */
+               o = parse_object(t->tagged->sha1);
+               deref = 1;
+       }
+       if (o && o->type == commit_type) {
+               struct commit *commit = (struct commit *)o;
+
+               if (!strncmp(path, "refs/heads/", 11))
+                       path = path + 11;
+               else if (!strncmp(path, "refs/", 5))
+                       path = path + 5;
+
+               name_rev(commit, strdup(path), 0, 0, deref);
+       }
+       return 0;
+}
+
+/* returns a static buffer */
+static const char* get_rev_name(struct object *o)
+{
+       static char buffer[1024];
+       struct rev_name *n = (struct rev_name *)o->util;
+       if (!n)
+               return "undefined";
+
+       if (!n->generation)
+               return n->tip_name;
+
+       snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation);
+
+       return buffer;
+}
+       
+int main(int argc, char **argv)
+{
+       struct object_list *revs = NULL;
+       struct object_list **walker = &revs;
+       int as_is = 0, all = 0, transform_stdin = 0;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       if (argc < 2)
+               usage(name_rev_usage);
+
+       for (--argc, ++argv; argc; --argc, ++argv) {
+               unsigned char sha1[20];
+               struct object *o;
+               struct commit *commit;
+
+               if (!as_is && (*argv)[0] == '-') {
+                       if (!strcmp(*argv, "--")) {
+                               as_is = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--tags")) {
+                               tags_only = 1;
+                               continue;
+                       } else if (!strcmp(*argv, "--all")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --all, not both!");
+                               all = 1;
+                               cutoff = 0;
+                               continue;
+                       } else if (!strcmp(*argv, "--stdin")) {
+                               if (argc > 1)
+                                       die("Specify either a list, or --stdin, not both!");
+                               transform_stdin = 1;
+                               cutoff = 0;
+                               continue;
+                       }
+                       usage(name_rev_usage);
+               }
+
+               if (get_sha1(*argv, sha1)) {
+                       fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               o = deref_tag(parse_object(sha1), *argv, 0);
+               if (!o || o->type != commit_type) {
+                       fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+                                       *argv);
+                       continue;
+               }
+
+               commit = (struct commit *)o;
+
+               if (cutoff > commit->date)
+                       cutoff = commit->date;
+
+               object_list_append((struct object *)commit, walker);
+               (*walker)->name = *argv;
+               walker = &((*walker)->next);
+       }
+
+       for_each_ref(name_ref);
+
+       if (transform_stdin) {
+               char buffer[2048];
+               char *p, *p_start;
+
+               while (!feof(stdin)) {
+                       int forty = 0;
+                       p = fgets(buffer, sizeof(buffer), stdin);
+                       if (!p)
+                               break;
+
+                       for (p_start = p; *p; p++) {
+#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
+                               if (!ishex(*p))
+                                       forty = 0;
+                               else if (++forty == 40 &&
+                                               !ishex(*(p+1))) {
+                                       unsigned char sha1[40];
+                                       const char *name = "undefined";
+                                       char c = *(p+1);
+
+                                       forty = 0;
+
+                                       *(p+1) = 0;
+                                       if (!get_sha1(p - 39, sha1)) {
+                                               struct object *o =
+                                                       lookup_object(sha1);
+                                               if (o)
+                                                       name = get_rev_name(o);
+                                       }
+                                       *(p+1) = c;
+
+                                       if (!strcmp(name, "undefined"))
+                                               continue;
+
+                                       fwrite(p_start, p - p_start + 1, 1,
+                                              stdout);
+                                       printf(" (%s)", name);
+                                       p_start = p + 1;
+                               }
+                       }
+
+                       /* flush */
+                       if (p_start != p)
+                               fwrite(p_start, p - p_start, 1, stdout);
+               }
+       } else if (all) {
+               int i;
+
+               for (i = 0; i < obj_allocs; i++)
+                       if (objs[i])
+                               printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
+                                               get_rev_name(objs[i]));
+       } else
+               for ( ; revs; revs = revs->next)
+                       printf("%s %s\n", revs->name, get_rev_name(revs->item));
+
+       return 0;
+}
+
diff --git a/object.c b/object.c
new file mode 100644 (file)
index 0000000..9adc874
--- /dev/null
+++ b/object.c
@@ -0,0 +1,268 @@
+#include "cache.h"
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "tag.h"
+
+struct object **objs;
+static int nr_objs;
+int obj_allocs;
+
+int track_object_refs = 0;
+
+static int hashtable_index(const unsigned char *sha1)
+{
+       unsigned int i;
+       memcpy(&i, sha1, sizeof(unsigned int));
+       return (int)(i % obj_allocs);
+}
+
+static int find_object(const unsigned char *sha1)
+{
+       int i;
+
+       if (!objs)
+               return -1;
+
+       i = hashtable_index(sha1);
+       while (objs[i]) {
+               if (memcmp(sha1, objs[i]->sha1, 20) == 0)
+                       return i;
+               i++;
+               if (i == obj_allocs)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+struct object *lookup_object(const unsigned char *sha1)
+{
+       int pos = find_object(sha1);
+       if (pos >= 0)
+               return objs[pos];
+       return NULL;
+}
+
+void created_object(const unsigned char *sha1, struct object *obj)
+{
+       int pos;
+
+       obj->parsed = 0;
+       memcpy(obj->sha1, sha1, 20);
+       obj->type = NULL;
+       obj->refs = NULL;
+       obj->used = 0;
+
+       if (obj_allocs - 1 <= nr_objs * 2) {
+               int i, count = obj_allocs;
+               obj_allocs = (obj_allocs < 32 ? 32 : 2 * obj_allocs);
+               objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
+               memset(objs + count, 0, (obj_allocs - count)
+                               * sizeof(struct object *));
+               for (i = 0; i < obj_allocs; i++)
+                       if (objs[i]) {
+                               int j = find_object(objs[i]->sha1);
+                               if (j != i) {
+                                       j = -1 - j;
+                                       objs[j] = objs[i];
+                                       objs[i] = NULL;
+                               }
+                       }
+       }
+
+       pos = find_object(sha1);
+       if (pos >= 0)
+               die("Inserting %s twice\n", sha1_to_hex(sha1));
+       pos = -pos-1;
+
+       objs[pos] = obj;
+       nr_objs++;
+}
+
+struct object_refs *alloc_object_refs(unsigned count)
+{
+       struct object_refs *refs;
+       size_t size = sizeof(*refs) + count*sizeof(struct object *);
+
+       refs = xcalloc(1, size);
+       refs->count = count;
+       return refs;
+}
+
+static int compare_object_pointers(const void *a, const void *b)
+{
+       const struct object * const *pa = a;
+       const struct object * const *pb = b;
+       if (*pa == *pb)
+               return 0;
+       else if (*pa < *pb)
+               return -1;
+       else
+               return 1;
+}
+
+void set_object_refs(struct object *obj, struct object_refs *refs)
+{
+       unsigned int i, j;
+
+       /* Do not install empty list of references */
+       if (refs->count < 1) {
+               free(refs);
+               return;
+       }
+
+       /* Sort the list and filter out duplicates */
+       qsort(refs->ref, refs->count, sizeof(refs->ref[0]),
+             compare_object_pointers);
+       for (i = j = 1; i < refs->count; i++) {
+               if (refs->ref[i] != refs->ref[i - 1])
+                       refs->ref[j++] = refs->ref[i];
+       }
+       if (j < refs->count) {
+               /* Duplicates were found - reallocate list */
+               size_t size = sizeof(*refs) + j*sizeof(struct object *);
+               refs->count = j;
+               refs = xrealloc(refs, size);
+       }
+
+       for (i = 0; i < refs->count; i++)
+               refs->ref[i]->used = 1;
+       obj->refs = refs;
+}
+
+void mark_reachable(struct object *obj, unsigned int mask)
+{
+       if (!track_object_refs)
+               die("cannot do reachability with object refs turned off");
+       /* If we've been here already, don't bother */
+       if (obj->flags & mask)
+               return;
+       obj->flags |= mask;
+       if (obj->refs) {
+               const struct object_refs *refs = obj->refs;
+               unsigned i;
+               for (i = 0; i < refs->count; i++)
+                       mark_reachable(refs->ref[i], mask);
+       }
+}
+
+struct object *lookup_object_type(const unsigned char *sha1, const char *type)
+{
+       if (!type) {
+               return lookup_unknown_object(sha1);
+       } else if (!strcmp(type, blob_type)) {
+               return &lookup_blob(sha1)->object;
+       } else if (!strcmp(type, tree_type)) {
+               return &lookup_tree(sha1)->object;
+       } else if (!strcmp(type, commit_type)) {
+               return &lookup_commit(sha1)->object;
+       } else if (!strcmp(type, tag_type)) {
+               return &lookup_tag(sha1)->object;
+       } else {
+               error("Unknown type %s", type);
+               return NULL;
+       }
+}
+
+union any_object {
+       struct object object;
+       struct commit commit;
+       struct tree tree;
+       struct blob blob;
+       struct tag tag;
+};
+
+struct object *lookup_unknown_object(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               union any_object *ret = xcalloc(1, sizeof(*ret));
+               created_object(sha1, &ret->object);
+               ret->object.type = NULL;
+               return &ret->object;
+       }
+       return obj;
+}
+
+struct object *parse_object(const unsigned char *sha1)
+{
+       unsigned long size;
+       char type[20];
+       void *buffer = read_sha1_file(sha1, type, &size);
+       if (buffer) {
+               struct object *obj;
+               if (check_sha1_signature(sha1, buffer, size, type) < 0)
+                       printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
+               if (!strcmp(type, blob_type)) {
+                       struct blob *blob = lookup_blob(sha1);
+                       parse_blob_buffer(blob, buffer, size);
+                       obj = &blob->object;
+               } else if (!strcmp(type, tree_type)) {
+                       struct tree *tree = lookup_tree(sha1);
+                       obj = &tree->object;
+                       if (!tree->object.parsed) {
+                               parse_tree_buffer(tree, buffer, size);
+                               buffer = NULL;
+                       }
+               } else if (!strcmp(type, commit_type)) {
+                       struct commit *commit = lookup_commit(sha1);
+                       parse_commit_buffer(commit, buffer, size);
+                       if (!commit->buffer) {
+                               commit->buffer = buffer;
+                               buffer = NULL;
+                       }
+                       obj = &commit->object;
+               } else if (!strcmp(type, tag_type)) {
+                       struct tag *tag = lookup_tag(sha1);
+                       parse_tag_buffer(tag, buffer, size);
+                       obj = &tag->object;
+               } else {
+                       obj = NULL;
+               }
+               free(buffer);
+               return obj;
+       }
+       return NULL;
+}
+
+struct object_list *object_list_insert(struct object *item,
+                                      struct object_list **list_p)
+{
+       struct object_list *new_list = xmalloc(sizeof(struct object_list));
+        new_list->item = item;
+        new_list->next = *list_p;
+        *list_p = new_list;
+        return new_list;
+}
+
+void object_list_append(struct object *item,
+                       struct object_list **list_p)
+{
+       while (*list_p) {
+               list_p = &((*list_p)->next);
+       }
+       *list_p = xmalloc(sizeof(struct object_list));
+       (*list_p)->next = NULL;
+       (*list_p)->item = item;
+}
+
+unsigned object_list_length(struct object_list *list)
+{
+       unsigned ret = 0;
+       while (list) {
+               list = list->next;
+               ret++;
+       }
+       return ret;
+}
+
+int object_list_contains(struct object_list *list, struct object *obj)
+{
+       while (list) {
+               if (list->item == obj)
+                       return 1;
+               list = list->next;
+       }
+       return 0;
+}
diff --git a/object.h b/object.h
new file mode 100644 (file)
index 0000000..e08afbd
--- /dev/null
+++ b/object.h
@@ -0,0 +1,58 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+struct object_list {
+       struct object *item;
+       struct object_list *next;
+       const char *name;
+};
+
+struct object_refs {
+       unsigned count;
+       struct object *ref[FLEX_ARRAY]; /* more */
+};
+
+struct object {
+       unsigned parsed : 1;
+       unsigned used : 1;
+       unsigned int flags;
+       unsigned char sha1[20];
+       const char *type;
+       struct object_refs *refs;
+       void *util;
+};
+
+extern int track_object_refs;
+extern int obj_allocs;
+extern struct object **objs;
+
+/** Internal only **/
+struct object *lookup_object(const unsigned char *sha1);
+
+/** Returns the object, having looked it up as being the given type. **/
+struct object *lookup_object_type(const unsigned char *sha1, const char *type);
+
+void created_object(const unsigned char *sha1, struct object *obj);
+
+/** Returns the object, having parsed it to find out what it is. **/
+struct object *parse_object(const unsigned char *sha1);
+
+/** Returns the object, with potentially excess memory allocated. **/
+struct object *lookup_unknown_object(const unsigned  char *sha1);
+
+struct object_refs *alloc_object_refs(unsigned count);
+void set_object_refs(struct object *obj, struct object_refs *refs);
+
+void mark_reachable(struct object *obj, unsigned int mask);
+
+struct object_list *object_list_insert(struct object *item, 
+                                      struct object_list **list_p);
+
+void object_list_append(struct object *item,
+                       struct object_list **list_p);
+
+unsigned object_list_length(struct object_list *list);
+
+int object_list_contains(struct object_list *list, struct object *obj);
+
+#endif /* OBJECT_H */
diff --git a/pack-check.c b/pack-check.c
new file mode 100644 (file)
index 0000000..e575879
--- /dev/null
@@ -0,0 +1,161 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_packfile(struct packed_git *p)
+{
+       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;
+       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));
+
+       SHA1_Init(&ctx);
+       pack_base = p->pack_base;
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               return error("Packfile %s SHA1 mismatch with itself",
+                            p->pack_name);
+       if (memcmp(sha1, index_base + index_size - 40, 20))
+               return error("Packfile %s SHA1 mismatch with idx",
+                            p->pack_name);
+
+       /* 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.
+        */
+       for (i = err = 0; i < nr_objects; i++) {
+               unsigned char sha1[20];
+               struct pack_entry e;
+               void *data;
+               char type[20];
+               unsigned long size;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+               data = unpack_entry_gently(&e, type, &size);
+               if (!data) {
+                       err = error("cannot unpack %s from %s",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       continue;
+               }
+               if (check_sha1_signature(sha1, data, size, type)) {
+                       err = error("packed %s from %s is corrupt",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       free(data);
+                       continue;
+               }
+               free(data);
+       }
+
+       return err;
+}
+
+
+#define MAX_CHAIN 40
+
+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);
+       memset(chain_histogram, 0, sizeof(chain_histogram));
+
+       for (i = 0; i < nr_objects; i++) {
+               unsigned char sha1[20], base_sha1[20];
+               struct pack_entry e;
+               char type[20];
+               unsigned long size;
+               unsigned long store_size;
+               unsigned int delta_chain_length;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+
+               packed_object_info_detail(&e, type, &size, &store_size,
+                                         &delta_chain_length,
+                                         base_sha1);
+               printf("%s ", sha1_to_hex(sha1));
+               if (!delta_chain_length)
+                       printf("%-6s %lu %u\n", type, size, e.offset);
+               else {
+                       printf("%-6s %lu %u %u %s\n", type, size, e.offset,
+                              delta_chain_length, sha1_to_hex(base_sha1));
+                       if (delta_chain_length < MAX_CHAIN)
+                               chain_histogram[delta_chain_length]++;
+                       else
+                               chain_histogram[0]++;
+               }
+       }
+
+       for (i = 0; i < MAX_CHAIN; i++) {
+               if (!chain_histogram[i])
+                       continue;
+               printf("chain length %s %d: %d object%s\n",
+                      i ? "=" : ">=",
+                      i ? i : MAX_CHAIN,
+                      chain_histogram[i],
+                      1 < chain_histogram[i] ? "s" : "");
+       }
+}
+
+int verify_pack(struct packed_git *p, int verbose)
+{
+       unsigned long index_size = p->index_size;
+       void *index_base = p->index_base;
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = 0;
+       /* Verify SHA1 sum of the index file */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, index_base, index_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, index_base + index_size - 20, 20))
+               ret = error("Packfile index for %s SHA1 mismatch",
+                           p->pack_name);
+
+       if (!ret) {
+               /* Verify pack file */
+               use_packed_git(p);
+               ret = verify_packfile(p);
+               unuse_packed_git(p);
+       }
+
+       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);
+               }
+       }
+
+       return ret;
+}
diff --git a/pack-objects.c b/pack-objects.c
new file mode 100644 (file)
index 0000000..179560f
--- /dev/null
@@ -0,0 +1,1342 @@
+#include "cache.h"
+#include "object.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+#include "tree-walk.h"
+#include <sys/time.h>
+#include <signal.h>
+
+static const char pack_usage[] = "git-pack-objects [-q] [--no-reuse-delta] [--non-empty] [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+
+struct object_entry {
+       unsigned char sha1[20];
+       unsigned long size;     /* uncompressed size */
+       unsigned long offset;   /* offset into the final pack file;
+                                * nonzero if already written.
+                                */
+       unsigned int depth;     /* delta depth */
+       unsigned int delta_limit;       /* base adjustment for in-pack delta */
+       unsigned int hash;      /* name hint hash */
+       enum object_type type;
+       enum object_type in_pack_type;  /* could be delta */
+       unsigned long delta_size;       /* delta data size (uncompressed) */
+       struct object_entry *delta;     /* delta base object */
+       struct packed_git *in_pack;     /* already in pack */
+       unsigned int in_pack_offset;
+       struct object_entry *delta_child; /* delitified objects who bases me */
+       struct object_entry *delta_sibling; /* other deltified objects who
+                                            * uses the same base as me
+                                            */
+       int preferred_base;     /* we do not pack this, but is encouraged to
+                                * be used as the base objectto delta huge
+                                * objects against.
+                                */
+};
+
+/*
+ * Objects we are going to pack are colected in objects array (dynamically
+ * expanded).  nr_objects & nr_alloc controls this array.  They are stored
+ * in the order we see -- typically rev-list --objects order that gives us
+ * nice "minimum seek" order.
+ *
+ * sorted-by-sha ans sorted-by-type are arrays of pointers that point at
+ * elements in the objects array.  The former is used to build the pack
+ * index (lists object names in the ascending order to help offset lookup),
+ * and the latter is used to group similar things together by try_delta()
+ * heuristics.
+ */
+
+static unsigned char object_list_sha1[20];
+static int non_empty = 0;
+static int no_reuse_delta = 0;
+static int local = 0;
+static int incremental = 0;
+static struct object_entry **sorted_by_sha, **sorted_by_type;
+static struct object_entry *objects = NULL;
+static int nr_objects = 0, nr_alloc = 0, nr_result = 0;
+static const char *base_name;
+static unsigned char pack_file_sha1[20];
+static int progress = 1;
+static volatile sig_atomic_t progress_update = 0;
+
+/*
+ * The object names in objects array are hashed with this hashtable,
+ * to help looking up the entry by object name.  Binary search from
+ * sorted_by_sha is also possible but this was easier to code and faster.
+ * This hashtable is built after all the objects are seen.
+ */
+static int *object_ix = NULL;
+static int object_ix_hashsz = 0;
+
+/*
+ * Pack index for existing packs give us easy access to the offsets into
+ * corresponding pack file where each object's data starts, but the entries
+ * do not store the size of the compressed representation (uncompressed
+ * size is easily available by examining the pack entry header).  We build
+ * a hashtable of existing packs (pack_revindex), and keep reverse index
+ * here -- pack index file is sorted by object name mapping to offset; this
+ * pack_revindex[].revindex array is an ordered list of offsets, so if you
+ * know the offset of an object, next offset is where its packed
+ * representation ends.
+ */
+struct pack_revindex {
+       struct packed_git *p;
+       unsigned long *revindex;
+} *pack_revindex = NULL;
+static int pack_revindex_hashsz = 0;
+
+/*
+ * stats
+ */
+static int written = 0;
+static int written_delta = 0;
+static int reused = 0;
+static int reused_delta = 0;
+
+static int pack_revindex_ix(struct packed_git *p)
+{
+       unsigned long ui = (unsigned long)p;
+       int i;
+
+       ui = ui ^ (ui >> 16); /* defeat structure alignment */
+       i = (int)(ui % pack_revindex_hashsz);
+       while (pack_revindex[i].p) {
+               if (pack_revindex[i].p == p)
+                       return i;
+               if (++i == pack_revindex_hashsz)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+static void prepare_pack_ix(void)
+{
+       int num;
+       struct packed_git *p;
+       for (num = 0, p = packed_git; p; p = p->next)
+               num++;
+       if (!num)
+               return;
+       pack_revindex_hashsz = num * 11;
+       pack_revindex = xcalloc(sizeof(*pack_revindex), pack_revindex_hashsz);
+       for (p = packed_git; p; p = p->next) {
+               num = pack_revindex_ix(p);
+               num = - 1 - num;
+               pack_revindex[num].p = p;
+       }
+       /* revindex elements are lazily initialized */
+}
+
+static int cmp_offset(const void *a_, const void *b_)
+{
+       unsigned long a = *(unsigned long *) a_;
+       unsigned long b = *(unsigned long *) b_;
+       if (a < b)
+               return -1;
+       else if (a == b)
+               return 0;
+       else
+               return 1;
+}
+
+/*
+ * Ordered list of offsets of objects in the pack.
+ */
+static void prepare_pack_revindex(struct pack_revindex *rix)
+{
+       struct packed_git *p = rix->p;
+       int num_ent = num_packed_objects(p);
+       int i;
+       void *index = p->index_base + 256;
+
+       rix->revindex = xmalloc(sizeof(unsigned long) * (num_ent + 1));
+       for (i = 0; i < num_ent; i++) {
+               unsigned int hl = *((unsigned int *)(index + 24 * i));
+               rix->revindex[i] = ntohl(hl);
+       }
+       /* This knows the pack format -- the 20-byte trailer
+        * follows immediately after the last object data.
+        */
+       rix->revindex[num_ent] = p->pack_size - 20;
+       qsort(rix->revindex, num_ent, sizeof(unsigned long), cmp_offset);
+}
+
+static unsigned long find_packed_object_size(struct packed_git *p,
+                                            unsigned long ofs)
+{
+       int num;
+       int lo, hi;
+       struct pack_revindex *rix;
+       unsigned long *revindex;
+       num = pack_revindex_ix(p);
+       if (num < 0)
+               die("internal error: pack revindex uninitialized");
+       rix = &pack_revindex[num];
+       if (!rix->revindex)
+               prepare_pack_revindex(rix);
+       revindex = rix->revindex;
+       lo = 0;
+       hi = num_packed_objects(p) + 1;
+       do {
+               int mi = (lo + hi) / 2;
+               if (revindex[mi] == ofs) {
+                       return revindex[mi+1] - ofs;
+               }
+               else if (ofs < revindex[mi])
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       } while (lo < hi);
+       die("internal error: pack revindex corrupt");
+}
+
+static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
+{
+       unsigned long othersize, delta_size;
+       char type[10];
+       void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize);
+       void *delta_buf;
+
+       if (!otherbuf)
+               die("unable to read %s", sha1_to_hex(entry->delta->sha1));
+        delta_buf = diff_delta(otherbuf, othersize,
+                              buf, size, &delta_size, 0);
+        if (!delta_buf || delta_size != entry->delta_size)
+               die("delta size changed");
+        free(buf);
+        free(otherbuf);
+       return delta_buf;
+}
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ *  - first byte: low four bits are "size", then three bits of "type",
+ *    and the high bit is "size continues".
+ *  - each byte afterwards: low seven bits are size continuation,
+ *    with the high bit being "size continues"
+ */
+static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
+{
+       int n = 1;
+       unsigned char c;
+
+       if (type < OBJ_COMMIT || type > OBJ_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 unsigned long write_object(struct sha1file *f,
+                                 struct object_entry *entry)
+{
+       unsigned long size;
+       char type[10];
+       void *buf;
+       unsigned char header[10];
+       unsigned hdrlen, datalen;
+       enum object_type obj_type;
+       int to_reuse = 0;
+
+       if (entry->preferred_base)
+               return 0;
+
+       obj_type = entry->type;
+       if (! entry->in_pack)
+               to_reuse = 0;   /* can't reuse what we don't have */
+       else if (obj_type == OBJ_DELTA)
+               to_reuse = 1;   /* check_object() decided it for us */
+       else if (obj_type != entry->in_pack_type)
+               to_reuse = 0;   /* pack has delta which is unusable */
+       else if (entry->delta)
+               to_reuse = 0;   /* we want to pack afresh */
+       else
+               to_reuse = 1;   /* we have it in-pack undeltified,
+                                * and we do not need to deltify it.
+                                */
+
+       if (! to_reuse) {
+               buf = read_sha1_file(entry->sha1, type, &size);
+               if (!buf)
+                       die("unable to read %s", sha1_to_hex(entry->sha1));
+               if (size != entry->size)
+                       die("object %s size inconsistency (%lu vs %lu)",
+                           sha1_to_hex(entry->sha1), size, entry->size);
+               if (entry->delta) {
+                       buf = delta_against(buf, size, entry);
+                       size = entry->delta_size;
+                       obj_type = OBJ_DELTA;
+               }
+               /*
+                * The object header is a byte of 'type' followed by zero or
+                * more bytes of length.  For deltas, the 20 bytes of delta
+                * sha1 follows that.
+                */
+               hdrlen = encode_header(obj_type, size, header);
+               sha1write(f, header, hdrlen);
+
+               if (entry->delta) {
+                       sha1write(f, entry->delta, 20);
+                       hdrlen += 20;
+               }
+               datalen = sha1write_compressed(f, buf, size);
+               free(buf);
+       }
+       else {
+               struct packed_git *p = entry->in_pack;
+               use_packed_git(p);
+
+               datalen = find_packed_object_size(p, entry->in_pack_offset);
+               buf = p->pack_base + entry->in_pack_offset;
+               sha1write(f, buf, datalen);
+               unuse_packed_git(p);
+               hdrlen = 0; /* not really */
+               if (obj_type == OBJ_DELTA)
+                       reused_delta++;
+               reused++;
+       }
+       if (obj_type == OBJ_DELTA)
+               written_delta++;
+       written++;
+       return hdrlen + datalen;
+}
+
+static unsigned long write_one(struct sha1file *f,
+                              struct object_entry *e,
+                              unsigned long offset)
+{
+       if (e->offset)
+               /* offset starts from header size and cannot be zero
+                * if it is written already.
+                */
+               return offset;
+       e->offset = offset;
+       offset += write_object(f, e);
+       /* if we are deltified, write out its base object. */
+       if (e->delta)
+               offset = write_one(f, e->delta, offset);
+       return offset;
+}
+
+static void write_pack_file(void)
+{
+       int i;
+       struct sha1file *f;
+       unsigned long offset;
+       struct pack_header hdr;
+       unsigned last_percent = 999;
+       int do_progress = 0;
+
+       if (!base_name)
+               f = sha1fd(1, "<stdout>");
+       else {
+               f = sha1create("%s-%s.%s", base_name,
+                              sha1_to_hex(object_list_sha1), "pack");
+               do_progress = progress;
+       }
+       if (do_progress)
+               fprintf(stderr, "Writing %d objects.\n", nr_result);
+
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(PACK_VERSION);
+       hdr.hdr_entries = htonl(nr_result);
+       sha1write(f, &hdr, sizeof(hdr));
+       offset = sizeof(hdr);
+       if (!nr_result)
+               goto done;
+       for (i = 0; i < nr_objects; i++) {
+               offset = write_one(f, objects + i, offset);
+               if (do_progress) {
+                       unsigned percent = written * 100 / nr_result;
+                       if (progress_update || percent != last_percent) {
+                               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percent, written, nr_result);
+                               progress_update = 0;
+                               last_percent = percent;
+                       }
+               }
+       }
+       if (do_progress)
+               fputc('\n', stderr);
+ done:
+       sha1close(f, pack_file_sha1, 1);
+}
+
+static void write_index_file(void)
+{
+       int i;
+       struct sha1file *f = sha1create("%s-%s.%s", base_name,
+                                       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];
+
+       /*
+        * Write the first-level table (the list is sorted,
+        * but we use a 256-entry lookup to be able to avoid
+        * having to do eight extra binary search iterations).
+        */
+       for (i = 0; i < 256; i++) {
+               struct object_entry **next = list;
+               while (next < last) {
+                       struct object_entry *entry = *next;
+                       if (entry->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * sizeof(int));
+
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_result; i++) {
+               struct object_entry *entry = *list++;
+               unsigned int offset = htonl(entry->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, entry->sha1, 20);
+       }
+       sha1write(f, pack_file_sha1, 20);
+       sha1close(f, NULL, 1);
+}
+
+static int locate_object_entry_hash(const unsigned char *sha1)
+{
+       int i;
+       unsigned int ui;
+       memcpy(&ui, sha1, sizeof(unsigned int));
+       i = ui % object_ix_hashsz;
+       while (0 < object_ix[i]) {
+               if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20))
+                       return i;
+               if (++i == object_ix_hashsz)
+                       i = 0;
+       }
+       return -1 - i;
+}
+
+static struct object_entry *locate_object_entry(const unsigned char *sha1)
+{
+       int i;
+
+       if (!object_ix_hashsz)
+               return NULL;
+
+       i = locate_object_entry_hash(sha1);
+       if (0 <= i)
+               return &objects[object_ix[i]-1];
+       return NULL;
+}
+
+static void rehash_objects(void)
+{
+       int i;
+       struct object_entry *oe;
+
+       object_ix_hashsz = nr_objects * 3;
+       if (object_ix_hashsz < 1024)
+               object_ix_hashsz = 1024;
+       object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
+       memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
+       for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
+               int ix = locate_object_entry_hash(oe->sha1);
+               if (0 <= ix)
+                       continue;
+               ix = -1 - ix;
+               object_ix[ix] = i + 1;
+       }
+}
+
+static unsigned name_hash(const char *name)
+{
+       unsigned char c;
+       unsigned hash = 0;
+
+       /*
+        * This effectively just creates a sortable number from the
+        * last sixteen non-whitespace characters. Last characters
+        * count "most", so things that end in ".c" sort together.
+        */
+       while ((c = *name++) != 0) {
+               if (isspace(c))
+                       continue;
+               hash = (hash >> 2) + (c << 24);
+       }
+       return hash;
+}
+
+static int add_object_entry(const unsigned char *sha1, unsigned hash, int exclude)
+{
+       unsigned int idx = nr_objects;
+       struct object_entry *entry;
+       struct packed_git *p;
+       unsigned int found_offset = 0;
+       struct packed_git *found_pack = NULL;
+       int ix, status = 0;
+
+       if (!exclude) {
+               for (p = packed_git; p; p = p->next) {
+                       struct pack_entry e;
+                       if (find_pack_entry_one(sha1, &e, p)) {
+                               if (incremental)
+                                       return 0;
+                               if (local && !p->pack_local)
+                                       return 0;
+                               if (!found_pack) {
+                                       found_offset = e.offset;
+                                       found_pack = e.p;
+                               }
+                       }
+               }
+       }
+       if ((entry = locate_object_entry(sha1)) != NULL)
+               goto already_added;
+
+       if (idx >= nr_alloc) {
+               unsigned int needed = (idx + 1024) * 3 / 2;
+               objects = xrealloc(objects, needed * sizeof(*entry));
+               nr_alloc = needed;
+       }
+       entry = objects + idx;
+       nr_objects = idx + 1;
+       memset(entry, 0, sizeof(*entry));
+       memcpy(entry->sha1, sha1, 20);
+       entry->hash = hash;
+
+       if (object_ix_hashsz * 3 <= nr_objects * 4)
+               rehash_objects();
+       else {
+               ix = locate_object_entry_hash(entry->sha1);
+               if (0 <= ix)
+                       die("internal error in object hashing.");
+               object_ix[-1 - ix] = idx + 1;
+       }
+       status = 1;
+
+ already_added:
+       if (progress_update) {
+               fprintf(stderr, "Counting objects...%d\r", nr_objects);
+               progress_update = 0;
+       }
+       if (exclude)
+               entry->preferred_base = 1;
+       else {
+               if (found_pack) {
+                       entry->in_pack = found_pack;
+                       entry->in_pack_offset = found_offset;
+               }
+       }
+       return status;
+}
+
+struct pbase_tree_cache {
+       unsigned char sha1[20];
+       int ref;
+       int temporary;
+       void *tree_data;
+       unsigned long tree_size;
+};
+
+static struct pbase_tree_cache *(pbase_tree_cache[256]);
+static int pbase_tree_cache_ix(const unsigned char *sha1)
+{
+       return sha1[0] % ARRAY_SIZE(pbase_tree_cache);
+}
+static int pbase_tree_cache_ix_incr(int ix)
+{
+       return (ix+1) % ARRAY_SIZE(pbase_tree_cache);
+}
+
+static struct pbase_tree {
+       struct pbase_tree *next;
+       /* This is a phony "cache" entry; we are not
+        * going to evict it nor find it through _get()
+        * mechanism -- this is for the toplevel node that
+        * would almost always change with any commit.
+        */
+       struct pbase_tree_cache pcache;
+} *pbase_tree;
+
+static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1)
+{
+       struct pbase_tree_cache *ent, *nent;
+       void *data;
+       unsigned long size;
+       char type[20];
+       int neigh;
+       int my_ix = pbase_tree_cache_ix(sha1);
+       int available_ix = -1;
+
+       /* pbase-tree-cache acts as a limited hashtable.
+        * your object will be found at your index or within a few
+        * slots after that slot if it is cached.
+        */
+       for (neigh = 0; neigh < 8; neigh++) {
+               ent = pbase_tree_cache[my_ix];
+               if (ent && !memcmp(ent->sha1, sha1, 20)) {
+                       ent->ref++;
+                       return ent;
+               }
+               else if (((available_ix < 0) && (!ent || !ent->ref)) ||
+                        ((0 <= available_ix) &&
+                         (!ent && pbase_tree_cache[available_ix])))
+                       available_ix = my_ix;
+               if (!ent)
+                       break;
+               my_ix = pbase_tree_cache_ix_incr(my_ix);
+       }
+
+       /* Did not find one.  Either we got a bogus request or
+        * we need to read and perhaps cache.
+        */
+       data = read_sha1_file(sha1, type, &size);
+       if (!data)
+               return NULL;
+       if (strcmp(type, tree_type)) {
+               free(data);
+               return NULL;
+       }
+
+       /* We need to either cache or return a throwaway copy */
+
+       if (available_ix < 0)
+               ent = NULL;
+       else {
+               ent = pbase_tree_cache[available_ix];
+               my_ix = available_ix;
+       }
+
+       if (!ent) {
+               nent = xmalloc(sizeof(*nent));
+               nent->temporary = (available_ix < 0);
+       }
+       else {
+               /* evict and reuse */
+               free(ent->tree_data);
+               nent = ent;
+       }
+       memcpy(nent->sha1, sha1, 20);
+       nent->tree_data = data;
+       nent->tree_size = size;
+       nent->ref = 1;
+       if (!nent->temporary)
+               pbase_tree_cache[my_ix] = nent;
+       return nent;
+}
+
+static void pbase_tree_put(struct pbase_tree_cache *cache)
+{
+       if (!cache->temporary) {
+               cache->ref--;
+               return;
+       }
+       free(cache->tree_data);
+       free(cache);
+}
+
+static int name_cmp_len(const char *name)
+{
+       int i;
+       for (i = 0; name[i] && name[i] != '\n' && name[i] != '/'; i++)
+               ;
+       return i;
+}
+
+static void add_pbase_object(struct tree_desc *tree,
+                            const char *name,
+                            int cmplen,
+                            const char *fullname)
+{
+       struct name_entry entry;
+
+       while (tree_entry(tree,&entry)) {
+               unsigned long size;
+               char type[20];
+
+               if (entry.pathlen != cmplen ||
+                   memcmp(entry.path, name, cmplen) ||
+                   !has_sha1_file(entry.sha1) ||
+                   sha1_object_info(entry.sha1, type, &size))
+                       continue;
+               if (name[cmplen] != '/') {
+                       unsigned hash = name_hash(fullname);
+                       add_object_entry(entry.sha1, hash, 1);
+                       return;
+               }
+               if (!strcmp(type, tree_type)) {
+                       struct tree_desc sub;
+                       struct pbase_tree_cache *tree;
+                       const char *down = name+cmplen+1;
+                       int downlen = name_cmp_len(down);
+
+                       tree = pbase_tree_get(entry.sha1);
+                       if (!tree)
+                               return;
+                       sub.buf = tree->tree_data;
+                       sub.size = tree->tree_size;
+
+                       add_pbase_object(&sub, down, downlen, fullname);
+                       pbase_tree_put(tree);
+               }
+       }
+}
+
+static unsigned *done_pbase_paths;
+static int done_pbase_paths_num;
+static int done_pbase_paths_alloc;
+static int done_pbase_path_pos(unsigned hash)
+{
+       int lo = 0;
+       int hi = done_pbase_paths_num;
+       while (lo < hi) {
+               int mi = (hi + lo) / 2;
+               if (done_pbase_paths[mi] == hash)
+                       return mi;
+               if (done_pbase_paths[mi] < hash)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo-1;
+}
+
+static int check_pbase_path(unsigned hash)
+{
+       int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash);
+       if (0 <= pos)
+               return 1;
+       pos = -pos - 1;
+       if (done_pbase_paths_alloc <= done_pbase_paths_num) {
+               done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
+               done_pbase_paths = xrealloc(done_pbase_paths,
+                                           done_pbase_paths_alloc *
+                                           sizeof(unsigned));
+       }
+       done_pbase_paths_num++;
+       if (pos < done_pbase_paths_num)
+               memmove(done_pbase_paths + pos + 1,
+                       done_pbase_paths + pos,
+                       (done_pbase_paths_num - pos - 1) * sizeof(unsigned));
+       done_pbase_paths[pos] = hash;
+       return 0;
+}
+
+static void add_preferred_base_object(char *name, unsigned hash)
+{
+       struct pbase_tree *it;
+       int cmplen = name_cmp_len(name);
+
+       if (check_pbase_path(hash))
+               return;
+
+       for (it = pbase_tree; it; it = it->next) {
+               if (cmplen == 0) {
+                       hash = name_hash("");
+                       add_object_entry(it->pcache.sha1, hash, 1);
+               }
+               else {
+                       struct tree_desc tree;
+                       tree.buf = it->pcache.tree_data;
+                       tree.size = it->pcache.tree_size;
+                       add_pbase_object(&tree, name, cmplen, name);
+               }
+       }
+}
+
+static void add_preferred_base(unsigned char *sha1)
+{
+       struct pbase_tree *it;
+       void *data;
+       unsigned long size;
+       unsigned char tree_sha1[20];
+
+       data = read_object_with_reference(sha1, tree_type, &size, tree_sha1);
+       if (!data)
+               return;
+
+       for (it = pbase_tree; it; it = it->next) {
+               if (!memcmp(it->pcache.sha1, tree_sha1, 20)) {
+                       free(data);
+                       return;
+               }
+       }
+
+       it = xcalloc(1, sizeof(*it));
+       it->next = pbase_tree;
+       pbase_tree = it;
+
+       memcpy(it->pcache.sha1, tree_sha1, 20);
+       it->pcache.tree_data = data;
+       it->pcache.tree_size = size;
+}
+
+static void check_object(struct object_entry *entry)
+{
+       char type[20];
+
+       if (entry->in_pack && !entry->preferred_base) {
+               unsigned char base[20];
+               unsigned long size;
+               struct object_entry *base_entry;
+
+               /* We want in_pack_type even if we do not reuse delta.
+                * There is no point not reusing non-delta representations.
+                */
+               check_reuse_pack_delta(entry->in_pack,
+                                      entry->in_pack_offset,
+                                      base, &size,
+                                      &entry->in_pack_type);
+
+               /* Check if it is delta, and the base is also an object
+                * we are going to pack.  If so we will reuse the existing
+                * delta.
+                */
+               if (!no_reuse_delta &&
+                   entry->in_pack_type == OBJ_DELTA &&
+                   (base_entry = locate_object_entry(base)) &&
+                   (!base_entry->preferred_base)) {
+
+                       /* Depth value does not matter - find_deltas()
+                        * will never consider reused delta as the
+                        * base object to deltify other objects
+                        * against, in order to avoid circular deltas.
+                        */
+
+                       /* uncompressed size of the delta data */
+                       entry->size = entry->delta_size = size;
+                       entry->delta = base_entry;
+                       entry->type = OBJ_DELTA;
+
+                       entry->delta_sibling = base_entry->delta_child;
+                       base_entry->delta_child = entry;
+
+                       return;
+               }
+               /* Otherwise we would do the usual */
+       }
+
+       if (sha1_object_info(entry->sha1, type, &entry->size))
+               die("unable to get type of object %s",
+                   sha1_to_hex(entry->sha1));
+
+       if (!strcmp(type, commit_type)) {
+               entry->type = OBJ_COMMIT;
+       } else if (!strcmp(type, tree_type)) {
+               entry->type = OBJ_TREE;
+       } else if (!strcmp(type, blob_type)) {
+               entry->type = OBJ_BLOB;
+       } else if (!strcmp(type, tag_type)) {
+               entry->type = OBJ_TAG;
+       } else
+               die("unable to pack object %s of type %s",
+                   sha1_to_hex(entry->sha1), type);
+}
+
+static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
+{
+       struct object_entry *child = me->delta_child;
+       unsigned int m = n;
+       while (child) {
+               unsigned int c = check_delta_limit(child, n + 1);
+               if (m < c)
+                       m = c;
+               child = child->delta_sibling;
+       }
+       return m;
+}
+
+static void get_object_details(void)
+{
+       int i;
+       struct object_entry *entry;
+
+       prepare_pack_ix();
+       for (i = 0, entry = objects; i < nr_objects; i++, entry++)
+               check_object(entry);
+
+       if (nr_objects == nr_result) {
+               /*
+                * Depth of objects that depend on the entry -- this
+                * is subtracted from depth-max to break too deep
+                * delta chain because of delta data reusing.
+                * However, we loosen this restriction when we know we
+                * are creating a thin pack -- it will have to be
+                * expanded on the other end anyway, so do not
+                * artificially cut the delta chain and let it go as
+                * deep as it wants.
+                */
+               for (i = 0, entry = objects; i < nr_objects; i++, entry++)
+                       if (!entry->delta && entry->delta_child)
+                               entry->delta_limit =
+                                       check_delta_limit(entry, 1);
+       }
+}
+
+typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *);
+
+static entry_sort_t current_sort;
+
+static int sort_comparator(const void *_a, const void *_b)
+{
+       struct object_entry *a = *(struct object_entry **)_a;
+       struct object_entry *b = *(struct object_entry **)_b;
+       return current_sort(a,b);
+}
+
+static struct object_entry **create_sorted_list(entry_sort_t sort)
+{
+       struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *));
+       int i;
+
+       for (i = 0; i < nr_objects; i++)
+               list[i] = objects + i;
+       current_sort = sort;
+       qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator);
+       return list;
+}
+
+static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
+{
+       return memcmp(a->sha1, b->sha1, 20);
+}
+
+static struct object_entry **create_final_object_list(void)
+{
+       struct object_entry **list;
+       int i, j;
+
+       for (i = nr_result = 0; i < nr_objects; i++)
+               if (!objects[i].preferred_base)
+                       nr_result++;
+       list = xmalloc(nr_result * sizeof(struct object_entry *));
+       for (i = j = 0; i < nr_objects; i++) {
+               if (!objects[i].preferred_base)
+                       list[j++] = objects + i;
+       }
+       current_sort = sha1_sort;
+       qsort(list, nr_result, sizeof(struct object_entry *), sort_comparator);
+       return list;
+}
+
+static int type_size_sort(const struct object_entry *a, const struct object_entry *b)
+{
+       if (a->type < b->type)
+               return -1;
+       if (a->type > b->type)
+               return 1;
+       if (a->hash < b->hash)
+               return -1;
+       if (a->hash > b->hash)
+               return 1;
+       if (a->preferred_base < b->preferred_base)
+               return -1;
+       if (a->preferred_base > b->preferred_base)
+               return 1;
+       if (a->size < b->size)
+               return -1;
+       if (a->size > b->size)
+               return 1;
+       return a < b ? -1 : (a > b);
+}
+
+struct unpacked {
+       struct object_entry *entry;
+       void *data;
+       struct delta_index *index;
+};
+
+/*
+ * We search for deltas _backwards_ in a list sorted by type and
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller - deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one.
+ */
+static int try_delta(struct unpacked *trg, struct unpacked *src,
+                    struct delta_index *src_index, unsigned max_depth)
+{
+       struct object_entry *trg_entry = trg->entry;
+       struct object_entry *src_entry = src->entry;
+       unsigned long size, src_size, delta_size, sizediff, max_size;
+       void *delta_buf;
+
+       /* Don't bother doing diffs between different types */
+       if (trg_entry->type != src_entry->type)
+               return -1;
+
+       /* We do not compute delta to *create* objects we are not
+        * going to pack.
+        */
+       if (trg_entry->preferred_base)
+               return -1;
+
+       /*
+        * If the current object is at pack edge, take the depth the
+        * objects that depend on the current object into account --
+        * otherwise they would become too deep.
+        */
+       if (trg_entry->delta_child) {
+               if (max_depth <= trg_entry->delta_limit)
+                       return 0;
+               max_depth -= trg_entry->delta_limit;
+       }
+       if (src_entry->depth >= max_depth)
+               return 0;
+
+       /* Now some size filtering heuristics. */
+       size = trg_entry->size;
+       max_size = size/2 - 20;
+       max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+       if (max_size == 0)
+               return 0;
+       if (trg_entry->delta && trg_entry->delta_size <= max_size)
+               max_size = trg_entry->delta_size-1;
+       src_size = src_entry->size;
+       sizediff = src_size < size ? size - src_size : 0;
+       if (sizediff >= max_size)
+               return 0;
+
+       delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size);
+       if (!delta_buf)
+               return 0;
+
+       trg_entry->delta = src_entry;
+       trg_entry->delta_size = delta_size;
+       trg_entry->depth = src_entry->depth + 1;
+       free(delta_buf);
+       return 1;
+}
+
+static void progress_interval(int signum)
+{
+       progress_update = 1;
+}
+
+static void find_deltas(struct object_entry **list, int window, int depth)
+{
+       int i, idx;
+       unsigned int array_size = window * sizeof(struct unpacked);
+       struct unpacked *array = xmalloc(array_size);
+       unsigned processed = 0;
+       unsigned last_percent = 999;
+
+       memset(array, 0, array_size);
+       i = nr_objects;
+       idx = 0;
+       if (progress)
+               fprintf(stderr, "Deltifying %d objects.\n", nr_result);
+
+       while (--i >= 0) {
+               struct object_entry *entry = list[i];
+               struct unpacked *n = array + idx;
+               unsigned long size;
+               char type[10];
+               int j;
+
+               if (!entry->preferred_base)
+                       processed++;
+
+               if (progress) {
+                       unsigned percent = processed * 100 / nr_result;
+                       if (percent != last_percent || progress_update) {
+                               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                                       percent, processed, nr_result);
+                               progress_update = 0;
+                               last_percent = percent;
+                       }
+               }
+
+               if (entry->delta)
+                       /* This happens if we decided to reuse existing
+                        * delta from a pack.  "!no_reuse_delta &&" is implied.
+                        */
+                       continue;
+
+               if (entry->size < 50)
+                       continue;
+               free_delta_index(n->index);
+               n->index = NULL;
+               free(n->data);
+               n->entry = entry;
+               n->data = read_sha1_file(entry->sha1, type, &size);
+               if (size != entry->size)
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(entry->sha1), size, entry->size);
+
+               j = window;
+               while (--j > 0) {
+                       unsigned int other_idx = idx + j;
+                       struct unpacked *m;
+                       if (other_idx >= window)
+                               other_idx -= window;
+                       m = array + other_idx;
+                       if (!m->entry)
+                               break;
+                       if (try_delta(n, m, m->index, depth) < 0)
+                               break;
+               }
+               /* if we made n a delta, and if n is already at max
+                * depth, leaving it in the window is pointless.  we
+                * should evict it first.
+                */
+               if (entry->delta && depth <= entry->depth)
+                       continue;
+
+               n->index = create_delta_index(n->data, size);
+               if (!n->index)
+                       die("out of memory");
+
+               idx++;
+               if (idx >= window)
+                       idx = 0;
+       }
+
+       if (progress)
+               fputc('\n', stderr);
+
+       for (i = 0; i < window; ++i) {
+               free_delta_index(array[i].index);
+               free(array[i].data);
+       }
+       free(array);
+}
+
+static void prepare_pack(int window, int depth)
+{
+       get_object_details();
+       sorted_by_type = create_sorted_list(type_size_sort);
+       if (window && depth)
+               find_deltas(sorted_by_type, window+1, depth);
+}
+
+static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
+{
+       static const char cache[] = "pack-cache/pack-%s.%s";
+       char *cached_pack, *cached_idx;
+       int ifd, ofd, ifd_ix = -1;
+
+       cached_pack = git_path(cache, sha1_to_hex(sha1), "pack");
+       ifd = open(cached_pack, O_RDONLY);
+       if (ifd < 0)
+               return 0;
+
+       if (!pack_to_stdout) {
+               cached_idx = git_path(cache, sha1_to_hex(sha1), "idx");
+               ifd_ix = open(cached_idx, O_RDONLY);
+               if (ifd_ix < 0) {
+                       close(ifd);
+                       return 0;
+               }
+       }
+
+       if (progress)
+               fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+                       sha1_to_hex(sha1));
+
+       if (pack_to_stdout) {
+               if (copy_fd(ifd, 1))
+                       exit(1);
+               close(ifd);
+       }
+       else {
+               char name[PATH_MAX];
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd, ofd))
+                       exit(1);
+               close(ifd);
+
+               snprintf(name, sizeof(name),
+                        "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx");
+               ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+               if (ofd < 0)
+                       die("unable to open %s (%s)", name, strerror(errno));
+               if (copy_fd(ifd_ix, ofd))
+                       exit(1);
+               close(ifd_ix);
+               puts(sha1_to_hex(sha1));
+       }
+
+       return 1;
+}
+
+static void setup_progress_signal(void)
+{
+       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);
+}
+
+int main(int argc, char **argv)
+{
+       SHA_CTX ctx;
+       char line[40 + 1 + PATH_MAX + 2];
+       int window = 10, depth = 10, pack_to_stdout = 0;
+       struct object_entry **list;
+       int num_preferred_base = 0;
+       int i;
+
+       setup_git_directory();
+
+       progress = isatty(2);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp("--non-empty", arg)) {
+                               non_empty = 1;
+                               continue;
+                       }
+                       if (!strcmp("--local", arg)) {
+                               local = 1;
+                               continue;
+                       }
+                       if (!strcmp("--incremental", arg)) {
+                               incremental = 1;
+                               continue;
+                       }
+                       if (!strncmp("--window=", arg, 9)) {
+                               char *end;
+                               window = strtoul(arg+9, &end, 0);
+                               if (!arg[9] || *end)
+                                       usage(pack_usage);
+                               continue;
+                       }
+                       if (!strncmp("--depth=", arg, 8)) {
+                               char *end;
+                               depth = strtoul(arg+8, &end, 0);
+                               if (!arg[8] || *end)
+                                       usage(pack_usage);
+                               continue;
+                       }
+                       if (!strcmp("--progress", arg)) {
+                               progress = 1;
+                               continue;
+                       }
+                       if (!strcmp("-q", arg)) {
+                               progress = 0;
+                               continue;
+                       }
+                       if (!strcmp("--no-reuse-delta", arg)) {
+                               no_reuse_delta = 1;
+                               continue;
+                       }
+                       if (!strcmp("--stdout", arg)) {
+                               pack_to_stdout = 1;
+                               continue;
+                       }
+                       usage(pack_usage);
+               }
+               if (base_name)
+                       usage(pack_usage);
+               base_name = arg;
+       }
+
+       if (pack_to_stdout != !base_name)
+               usage(pack_usage);
+
+       prepare_packed_git();
+
+       if (progress) {
+               fprintf(stderr, "Generating pack...\n");
+               setup_progress_signal();
+       }
+
+       for (;;) {
+               unsigned char sha1[20];
+               unsigned hash;
+
+               if (!fgets(line, sizeof(line), stdin)) {
+                       if (feof(stdin))
+                               break;
+                       if (!ferror(stdin))
+                               die("fgets returned NULL, not EOF, not error!");
+                       if (errno != EINTR)
+                               die("fgets: %s", strerror(errno));
+                       clearerr(stdin);
+                       continue;
+               }
+
+               if (line[0] == '-') {
+                       if (get_sha1_hex(line+1, sha1))
+                               die("expected edge sha1, got garbage:\n %s",
+                                   line+1);
+                       if (num_preferred_base++ < window)
+                               add_preferred_base(sha1);
+                       continue;
+               }
+               if (get_sha1_hex(line, sha1))
+                       die("expected sha1, got garbage:\n %s", line);
+               hash = name_hash(line+41);
+               add_preferred_base_object(line+41, hash);
+               add_object_entry(sha1, hash, 0);
+       }
+       if (progress)
+               fprintf(stderr, "Done counting %d objects.\n", nr_objects);
+       sorted_by_sha = create_final_object_list();
+       if (non_empty && !nr_result)
+               return 0;
+
+       SHA1_Init(&ctx);
+       list = sorted_by_sha;
+       for (i = 0; i < nr_result; i++) {
+               struct object_entry *entry = *list++;
+               SHA1_Update(&ctx, entry->sha1, 20);
+       }
+       SHA1_Final(object_list_sha1, &ctx);
+       if (progress && (nr_objects != nr_result))
+               fprintf(stderr, "Result has %d objects.\n", nr_result);
+
+       if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
+               ;
+       else {
+               if (nr_result)
+                       prepare_pack(window, depth);
+               if (progress && pack_to_stdout) {
+                       /* the other end usually displays progress itself */
+                       struct itimerval v = {{0,},};
+                       setitimer(ITIMER_REAL, &v, NULL);
+                       signal(SIGALRM, SIG_IGN );
+                       progress_update = 0;
+               }
+               write_pack_file();
+               if (!pack_to_stdout) {
+                       write_index_file();
+                       puts(sha1_to_hex(object_list_sha1));
+               }
+       }
+       if (progress)
+               fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
+                       nr_result, written, written_delta, reused, reused_delta);
+       return 0;
+}
diff --git a/pack-redundant.c b/pack-redundant.c
new file mode 100644 (file)
index 0000000..cd81f5a
--- /dev/null
@@ -0,0 +1,683 @@
+/*
+*
+* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
+*
+* This file is licensed under the GPL v2.
+*
+*/
+
+#include "cache.h"
+
+#define BLKSIZE 512
+
+static const char pack_redundant_usage[] =
+"git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+
+static int load_all_packs = 0, verbose = 0, alt_odb = 0;
+
+struct llist_item {
+       struct llist_item *next;
+       unsigned char *sha1;
+};
+static struct llist {
+       struct llist_item *front;
+       struct llist_item *back;
+       size_t size;
+} *all_objects; /* all objects which must be present in local packfiles */
+
+static struct pack_list {
+       struct pack_list *next;
+       struct packed_git *pack;
+       struct llist *unique_objects;
+       struct llist *all_objects;
+} *local_packs = NULL, *altodb_packs = NULL;
+
+struct pll {
+       struct pll *next;
+       struct pack_list *pl;
+};
+
+static struct llist_item *free_nodes = NULL;
+
+static inline void llist_item_put(struct llist_item *item)
+{
+       item->next = free_nodes;
+       free_nodes = item;
+}
+
+static inline struct llist_item *llist_item_get(void)
+{
+       struct llist_item *new;
+       if ( free_nodes ) {
+               new = free_nodes;
+               free_nodes = free_nodes->next;
+       } else {
+               int i = 1;
+               new = xmalloc(sizeof(struct llist_item) * BLKSIZE);
+               for(;i < BLKSIZE; i++) {
+                       llist_item_put(&new[i]);
+               }
+       }
+       return new;
+}
+
+static void llist_free(struct llist *list)
+{
+       while((list->back = list->front)) {
+               list->front = list->front->next;
+               llist_item_put(list->back);
+       }
+       free(list);
+}
+
+static inline void llist_init(struct llist **list)
+{
+       *list = xmalloc(sizeof(struct llist));
+       (*list)->front = (*list)->back = NULL;
+       (*list)->size = 0;
+}
+
+static struct llist * llist_copy(struct llist *list)
+{
+       struct llist *ret;
+       struct llist_item *new, *old, *prev;
+       
+       llist_init(&ret);
+
+       if ((ret->size = list->size) == 0)
+               return ret;
+
+       new = ret->front = llist_item_get();
+       new->sha1 = list->front->sha1;
+
+       old = list->front->next;
+       while (old) {
+               prev = new;
+               new = llist_item_get();
+               prev->next = new;
+               new->sha1 = old->sha1;
+               old = old->next;
+       }
+       new->next = NULL;
+       ret->back = new;
+       
+       return ret;
+}
+
+static inline struct llist_item * llist_insert(struct llist *list,
+                                              struct llist_item *after,
+                                              unsigned char *sha1)
+{
+       struct llist_item *new = llist_item_get();
+       new->sha1 = sha1;
+       new->next = NULL;
+
+       if (after != NULL) {
+               new->next = after->next;
+               after->next = new;
+               if (after == list->back)
+                       list->back = new;
+       } else {/* insert in front */
+               if (list->size == 0)
+                       list->back = new;
+               else
+                       new->next = list->front;
+               list->front = new;
+       }
+       list->size++;
+       return new;
+}
+
+static inline struct llist_item *llist_insert_back(struct llist *list, unsigned char *sha1)
+{
+       return llist_insert(list, list->back, sha1);
+}
+
+static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, unsigned char *sha1, struct llist_item *hint)
+{
+       struct llist_item *prev = NULL, *l;
+
+       l = (hint == NULL) ? list->front : hint;
+       while (l) {
+               int cmp = memcmp(l->sha1, sha1, 20);
+               if (cmp > 0) { /* we insert before this entry */
+                       return llist_insert(list, prev, sha1);
+               }
+               if(!cmp) { /* already exists */
+                       return l;
+               }
+               prev = l;
+               l = l->next;
+       }
+       /* insert at the end */
+       return llist_insert_back(list, sha1);
+}
+
+/* returns a pointer to an item in front of sha1 */
+static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint)
+{
+       struct llist_item *prev, *l;
+
+redo_from_start:
+       l = (hint == NULL) ? list->front : hint;
+       prev = NULL;
+       while (l) {
+               int cmp = memcmp(l->sha1, sha1, 20);
+               if (cmp > 0) /* not in list, since sorted */
+                       return prev;
+               if(!cmp) { /* found */
+                       if (prev == NULL) {
+                               if (hint != NULL && hint != list->front) {
+                                       /* we don't know the previous element */
+                                       hint = NULL;
+                                       goto redo_from_start;
+                               }
+                               list->front = l->next;
+                       } else
+                               prev->next = l->next;
+                       if (l == list->back)
+                               list->back = prev;
+                       llist_item_put(l);
+                       list->size--;
+                       return prev;
+               }
+               prev = l;
+               l = l->next;
+       }
+       return prev;
+}
+
+/* computes A\B */
+static void llist_sorted_difference_inplace(struct llist *A,
+                                    struct llist *B)
+{
+       struct llist_item *hint, *b;
+
+       hint = NULL;
+       b = B->front;
+
+       while (b) {
+               hint = llist_sorted_remove(A, b->sha1, hint);
+               b = b->next;
+       }
+}
+
+static inline struct pack_list * pack_list_insert(struct pack_list **pl,
+                                          struct pack_list *entry)
+{
+       struct pack_list *p = xmalloc(sizeof(struct pack_list));
+       memcpy(p, entry, sizeof(struct pack_list));
+       p->next = *pl;
+       *pl = p;
+       return p;
+}
+
+static inline size_t pack_list_size(struct pack_list *pl)
+{
+       size_t ret = 0;
+       while(pl) {
+               ret++;
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static struct pack_list * pack_list_difference(const struct pack_list *A,
+                                              const struct pack_list *B)
+{
+       struct pack_list *ret;
+       const struct pack_list *pl;
+
+       if (A == NULL)
+               return NULL;
+
+       pl = B;
+       while (pl != NULL) {
+               if (A->pack == pl->pack)
+                       return pack_list_difference(A->next, B);
+               pl = pl->next;
+       }
+       ret = xmalloc(sizeof(struct pack_list));
+       memcpy(ret, A, sizeof(struct pack_list));
+       ret->next = pack_list_difference(A->next, B);
+       return ret;
+}
+
+static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
+{
+       int p1_off, p2_off;
+       void *p1_base, *p2_base;
+       struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+       
+       p1_off = p2_off = 256 * 4 + 4;
+       p1_base = (void *)p1->pack->index_base;
+       p2_base = (void *)p2->pack->index_base;
+
+       while (p1_off <= p1->pack->index_size - 3 * 20 &&
+              p2_off <= p2->pack->index_size - 3 * 20)
+       {
+               int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+               /* cmp ~ p1 - p2 */
+               if (cmp == 0) {
+                       p1_hint = llist_sorted_remove(p1->unique_objects,
+                                       p1_base + p1_off, p1_hint);
+                       p2_hint = llist_sorted_remove(p2->unique_objects,
+                                       p1_base + p1_off, p2_hint);
+                       p1_off+=24;
+                       p2_off+=24;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off+=24;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off+=24;
+               }
+       }
+}
+
+static void pll_free(struct pll *l)
+{
+       struct pll *old;
+       struct pack_list *opl;
+
+       while (l) {
+               old = l;
+               while (l->pl) {
+                       opl = l->pl;
+                       l->pl = opl->next;
+                       free(opl);
+               }
+               l = l->next;
+               free(old);
+       }
+}
+
+/* all the permutations have to be free()d at the same time,
+ * since they refer to each other
+ */
+static struct pll * get_permutations(struct pack_list *list, int n)
+{
+       struct pll *subset, *ret = NULL, *new_pll = NULL, *pll;
+
+       if (list == NULL || pack_list_size(list) < n || n == 0)
+               return NULL;
+
+       if (n == 1) {
+               while (list) {
+                       new_pll = xmalloc(sizeof(pll));
+                       new_pll->pl = NULL;
+                       pack_list_insert(&new_pll->pl, list);
+                       new_pll->next = ret;
+                       ret = new_pll;
+                       list = list->next;
+               }
+               return ret;
+       }
+
+       while (list->next) {
+               subset = get_permutations(list->next, n - 1);
+               while (subset) {
+                       new_pll = xmalloc(sizeof(pll));
+                       new_pll->pl = subset->pl;
+                       pack_list_insert(&new_pll->pl, list);
+                       new_pll->next = ret;
+                       ret = new_pll;
+                       subset = subset->next;
+               }
+               list = list->next;
+       }
+       return ret;
+}
+
+static int is_superset(struct pack_list *pl, struct llist *list)
+{
+       struct llist *diff;
+
+       diff = llist_copy(list);
+
+       while (pl) {
+               llist_sorted_difference_inplace(diff, pl->all_objects);
+               if (diff->size == 0) { /* we're done */
+                       llist_free(diff);
+                       return 1;
+               }
+               pl = pl->next;
+       }
+       llist_free(diff);
+       return 0;
+}
+
+static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
+{
+       size_t ret = 0;
+       int p1_off, p2_off;
+       void *p1_base, *p2_base;
+
+       p1_off = p2_off = 256 * 4 + 4;
+       p1_base = (void *)p1->index_base;
+       p2_base = (void *)p2->index_base;
+
+       while (p1_off <= p1->index_size - 3 * 20 &&
+              p2_off <= p2->index_size - 3 * 20)
+       {
+               int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+               /* cmp ~ p1 - p2 */
+               if (cmp == 0) {
+                       ret++;
+                       p1_off+=24;
+                       p2_off+=24;
+                       continue;
+               }
+               if (cmp < 0) { /* p1 has the object, p2 doesn't */
+                       p1_off+=24;
+               } else { /* p2 has the object, p1 doesn't */
+                       p2_off+=24;
+               }
+       }
+       return ret;
+}
+
+/* another O(n^2) function ... */
+static size_t get_pack_redundancy(struct pack_list *pl)
+{
+       struct pack_list *subset;
+       size_t ret = 0;
+
+       if (pl == NULL)
+               return 0;
+
+       while ((subset = pl->next)) {
+               while(subset) {
+                       ret += sizeof_union(pl->pack, subset->pack);
+                       subset = subset->next;
+               }
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static inline size_t pack_set_bytecount(struct pack_list *pl)
+{
+       size_t ret = 0;
+       while (pl) {
+               ret += pl->pack->pack_size;
+               ret += pl->pack->index_size;
+               pl = pl->next;
+       }
+       return ret;
+}
+
+static void minimize(struct pack_list **min)
+{
+       struct pack_list *pl, *unique = NULL,
+               *non_unique = NULL, *min_perm = NULL;
+       struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
+       struct llist *missing;
+       size_t min_perm_size = (size_t)-1, perm_size;
+       int n;
+
+       pl = local_packs;
+       while (pl) {
+               if(pl->unique_objects->size)
+                       pack_list_insert(&unique, pl);
+               else
+                       pack_list_insert(&non_unique, pl);
+               pl = pl->next;
+       }
+       /* find out which objects are missing from the set of unique packs */
+       missing = llist_copy(all_objects);
+       pl = unique;
+       while (pl) {
+               llist_sorted_difference_inplace(missing, pl->all_objects);
+               pl = pl->next;
+       }
+
+       /* return if there are no objects missing from the unique set */
+       if (missing->size == 0) {
+               *min = unique;
+               return;
+       }
+
+       /* find the permutations which contain all missing objects */
+       for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) {
+               perm_all = perm = get_permutations(non_unique, n);
+               while (perm) {
+                       if (is_superset(perm->pl, missing)) {
+                               new_perm = xmalloc(sizeof(struct pll));
+                               memcpy(new_perm, perm, sizeof(struct pll));
+                               new_perm->next = perm_ok;
+                               perm_ok = new_perm;
+                       }
+                       perm = perm->next;
+               }
+               if (perm_ok)
+                       break;
+               pll_free(perm_all);
+       }
+       if (perm_ok == NULL)
+               die("Internal error: No complete sets found!\n");
+
+       /* find the permutation with the smallest size */
+       perm = perm_ok;
+       while (perm) {
+               perm_size = pack_set_bytecount(perm->pl);
+               if (min_perm_size > perm_size) {
+                       min_perm_size = perm_size;
+                       min_perm = perm->pl;
+               }
+               perm = perm->next;
+       }
+       *min = min_perm;
+       /* add the unique packs to the list */
+       pl = unique;
+       while(pl) {
+               pack_list_insert(min, pl);
+               pl = pl->next;
+       }
+}
+
+static void load_all_objects(void)
+{
+       struct pack_list *pl = local_packs;
+       struct llist_item *hint, *l;
+
+       llist_init(&all_objects);
+
+       while (pl) {
+               hint = NULL;
+               l = pl->all_objects->front;
+               while (l) {
+                       hint = llist_insert_sorted_unique(all_objects,
+                                                         l->sha1, hint);
+                       l = l->next;
+               }
+               pl = pl->next;
+       }
+       /* remove objects present in remote packs */
+       pl = altodb_packs;
+       while (pl) {
+               llist_sorted_difference_inplace(all_objects, pl->all_objects);
+               pl = pl->next;
+       }
+}
+
+/* this scales like O(n^2) */
+static void cmp_local_packs(void)
+{
+       struct pack_list *subset, *pl = local_packs;
+
+       while ((subset = pl)) {
+               while((subset = subset->next))
+                       cmp_two_packs(pl, subset);
+               pl = pl->next;
+       }
+}
+
+static void scan_alt_odb_packs(void)
+{
+       struct pack_list *local, *alt;
+
+       alt = altodb_packs;
+       while (alt) {
+               local = local_packs;
+               while (local) {
+                       llist_sorted_difference_inplace(local->unique_objects,
+                                                       alt->all_objects);
+                       local = local->next;
+               }
+               llist_sorted_difference_inplace(all_objects, alt->all_objects);
+               alt = alt->next;
+       }
+}
+
+static struct pack_list * add_pack(struct packed_git *p)
+{
+       struct pack_list l;
+       size_t off;
+       void *base;
+
+       if (!p->pack_local && !(alt_odb || verbose))
+               return NULL;
+
+       l.pack = p;
+       llist_init(&l.all_objects);
+
+       off = 256 * 4 + 4;
+       base = (void *)p->index_base;
+       while (off <= p->index_size - 3 * 20) {
+               llist_insert_back(l.all_objects, base + off);
+               off += 24;
+       }
+       /* this list will be pruned in cmp_two_packs later */
+       l.unique_objects = llist_copy(l.all_objects);
+       if (p->pack_local)
+               return pack_list_insert(&local_packs, &l);
+       else
+               return pack_list_insert(&altodb_packs, &l);
+}
+
+static struct pack_list * add_pack_file(char *filename)
+{
+       struct packed_git *p = packed_git;
+
+       if (strlen(filename) < 40)
+               die("Bad pack filename: %s\n", filename);
+
+       while (p) {
+               if (strstr(p->pack_name, filename))
+                       return add_pack(p);
+               p = p->next;
+       }
+       die("Filename %s not found in packed_git\n", filename);
+}
+
+static void load_all(void)
+{
+       struct packed_git *p = packed_git;
+
+       while (p) {
+               add_pack(p);
+               p = p->next;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       struct pack_list *min, *red, *pl;
+       struct llist *ignore;
+       unsigned char *sha1;
+       char buf[42]; /* 40 byte sha1 + \n + \0 */
+
+       setup_git_directory();
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if(!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               if(!strcmp(arg, "--all")) {
+                       load_all_packs = 1;
+                       continue;
+               }
+               if(!strcmp(arg, "--verbose")) {
+                       verbose = 1;
+                       continue;
+               }
+               if(!strcmp(arg, "--alt-odb")) {
+                       alt_odb = 1;
+                       continue;
+               }
+               if(*arg == '-')
+                       usage(pack_redundant_usage);
+               else
+                       break;
+       }
+
+       prepare_packed_git();
+
+       if (load_all_packs)
+               load_all();
+       else
+               while (*(argv + i) != NULL)
+                       add_pack_file(*(argv + i++));
+
+       if (local_packs == NULL)
+               die("Zero packs found!\n");
+
+       load_all_objects();
+
+       cmp_local_packs();
+       if (alt_odb)
+               scan_alt_odb_packs();
+
+       /* ignore objects given on stdin */
+       llist_init(&ignore);
+       if (!isatty(0)) {
+               while (fgets(buf, sizeof(buf), stdin)) {
+                       sha1 = xmalloc(20);
+                       if (get_sha1_hex(buf, sha1))
+                               die("Bad sha1 on stdin: %s", buf);
+                       llist_insert_sorted_unique(ignore, sha1, NULL);
+               }
+       }
+       llist_sorted_difference_inplace(all_objects, ignore);
+       pl = local_packs;
+       while (pl) {
+               llist_sorted_difference_inplace(pl->unique_objects, ignore);
+               pl = pl->next;
+       }
+
+       minimize(&min);
+
+       if (verbose) {
+               fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
+                       (unsigned long)pack_list_size(altodb_packs));
+               fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
+               pl = min;
+               while (pl) {
+                       fprintf(stderr, "\t%s\n", pl->pack->pack_name);
+                       pl = pl->next;
+               }
+               fprintf(stderr, "containing %lu duplicate objects "
+                               "with a total size of %lukb.\n",
+                       (unsigned long)get_pack_redundancy(min),
+                       (unsigned long)pack_set_bytecount(min)/1024);
+               fprintf(stderr, "A total of %lu unique objects were considered.\n",
+                       (unsigned long)all_objects->size);
+               fprintf(stderr, "Redundant packs (with indexes):\n");
+       }
+       pl = red = pack_list_difference(local_packs, min);
+       while (pl) {
+               printf("%s\n%s\n",
+                      sha1_pack_index_name(pl->pack->sha1),
+                      pl->pack->pack_name);
+               pl = pl->next;
+       }
+       if (verbose)
+               fprintf(stderr, "%luMB of redundant packs in total.\n",
+                       (unsigned long)pack_set_bytecount(red)/(1024*1024));
+
+       return 0;
+}
diff --git a/pack.h b/pack.h
new file mode 100644 (file)
index 0000000..694e0c5
--- /dev/null
+++ b/pack.h
@@ -0,0 +1,35 @@
+#ifndef PACK_H
+#define PACK_H
+
+/*
+ * The packed object type is stored in 3 bits.
+ * The type value 0 is a reserved prefix if ever there is more than 7
+ * object types, or any future format extensions.
+ */
+enum object_type {
+       OBJ_EXT = 0,
+       OBJ_COMMIT = 1,
+       OBJ_TREE = 2,
+       OBJ_BLOB = 3,
+       OBJ_TAG = 4,
+       /* 5/6 for future expansion */
+       OBJ_DELTA = 7,
+};
+
+/*
+ * Packed object header
+ */
+#define PACK_SIGNATURE 0x5041434b      /* "PACK" */
+#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;
+};
+
+extern int verify_pack(struct packed_git *, int);
+extern int check_reuse_pack_delta(struct packed_git *, unsigned long,
+                                 unsigned char *, unsigned long *,
+                                 enum object_type *);
+#endif
diff --git a/pager.c b/pager.c
new file mode 100644 (file)
index 0000000..9a30939
--- /dev/null
+++ b/pager.c
@@ -0,0 +1,53 @@
+#include "cache.h"
+
+/*
+ * This is split up from the rest of git so that we might do
+ * something different on Windows, for example.
+ */
+
+static void run_pager(const char *pager)
+{
+       execlp(pager, pager, NULL);
+       execl("/bin/sh", "sh", "-c", pager, NULL);
+}
+
+void setup_pager(void)
+{
+       pid_t pid;
+       int fd[2];
+       const char *pager = getenv("PAGER");
+
+       if (!isatty(1))
+               return;
+       if (!pager)
+               pager = "less";
+       else if (!*pager || !strcmp(pager, "cat"))
+               return;
+
+       if (pipe(fd) < 0)
+               return;
+       pid = fork();
+       if (pid < 0) {
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* return in the child */
+       if (!pid) {
+               dup2(fd[1], 1);
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* The original process turns into the PAGER */
+       dup2(fd[0], 0);
+       close(fd[0]);
+       close(fd[1]);
+
+       setenv("LESS", "-S", 0);
+       run_pager(pager);
+       die("unable to execute pager '%s'", pager);
+       exit(255);
+}
diff --git a/patch-delta.c b/patch-delta.c
new file mode 100644 (file)
index 0000000..8f318ed
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * patch-delta.c:
+ * recreate a buffer from a source and the delta produced by diff-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "delta.h"
+
+void *patch_delta(const void *src_buf, unsigned long src_size,
+                 const void *delta_buf, unsigned long delta_size,
+                 unsigned long *dst_size)
+{
+       const unsigned char *data, *top;
+       unsigned char *dst_buf, *out, cmd;
+       unsigned long size;
+
+       if (delta_size < DELTA_SIZE_MIN)
+               return NULL;
+
+       data = delta_buf;
+       top = delta_buf + delta_size;
+
+       /* make sure the orig file size matches what we expect */
+       size = get_delta_hdr_size(&data, top);
+       if (size != src_size)
+               return NULL;
+
+       /* now the result size */
+       size = get_delta_hdr_size(&data, top);
+       dst_buf = malloc(size + 1);
+       if (!dst_buf)
+               return NULL;
+       dst_buf[size] = 0;
+
+       out = dst_buf;
+       while (data < top) {
+               cmd = *data++;
+               if (cmd & 0x80) {
+                       unsigned long cp_off = 0, cp_size = 0;
+                       if (cmd & 0x01) cp_off = *data++;
+                       if (cmd & 0x02) cp_off |= (*data++ << 8);
+                       if (cmd & 0x04) cp_off |= (*data++ << 16);
+                       if (cmd & 0x08) cp_off |= (*data++ << 24);
+                       if (cmd & 0x10) cp_size = *data++;
+                       if (cmd & 0x20) cp_size |= (*data++ << 8);
+                       if (cmd & 0x40) cp_size |= (*data++ << 16);
+                       if (cp_size == 0) cp_size = 0x10000;
+                       if (cp_off + cp_size < cp_size ||
+                           cp_off + cp_size > src_size ||
+                           cp_size > size)
+                               goto bad;
+                       memcpy(out, src_buf + cp_off, cp_size);
+                       out += cp_size;
+                       size -= cp_size;
+               } else if (cmd) {
+                       if (cmd > size)
+                               goto bad;
+                       memcpy(out, data, cmd);
+                       out += cmd;
+                       data += cmd;
+                       size -= cmd;
+               } else {
+                       /*
+                        * cmd == 0 is reserved for future encoding
+                        * extensions. In the mean time we must fail when
+                        * encountering them (might be data corruption).
+                        */
+                       goto bad;
+               }
+       }
+
+       /* sanity check */
+       if (data != top || size != 0) {
+               bad:
+               free(dst_buf);
+               return NULL;
+       }
+
+       *dst_size = out - dst_buf;
+       return dst_buf;
+}
diff --git a/patch-id.c b/patch-id.c
new file mode 100644 (file)
index 0000000..edbc4aa
--- /dev/null
@@ -0,0 +1,82 @@
+#include "cache.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, SHA_CTX *c)
+{
+       unsigned char result[20];
+       char name[50];
+
+       if (!patchlen)
+               return;
+
+       SHA1_Final(result, c);
+       memcpy(name, sha1_to_hex(id), 41);
+       printf("%s %s\n", sha1_to_hex(result), name);
+       SHA1_Init(c);
+}
+
+static int remove_space(char *line)
+{
+       char *src = line;
+       char *dst = line;
+       unsigned char c;
+
+       while ((c = *src++) != '\0') {
+               if (!isspace(c))
+                       *dst++ = c;
+       }
+       return dst - line;
+}
+
+static void generate_id_list(void)
+{
+       static unsigned char sha1[20];
+       static char line[1000];
+       SHA_CTX ctx;
+       int patchlen = 0;
+
+       SHA1_Init(&ctx);
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               unsigned char n[20];
+               char *p = line;
+               int len;
+
+               if (!memcmp(line, "diff-tree ", 10))
+                       p += 10;
+
+               if (!get_sha1_hex(p, n)) {
+                       flush_current_id(patchlen, sha1, &ctx);
+                       memcpy(sha1, n, 20);
+                       patchlen = 0;
+                       continue;
+               }
+
+               /* Ignore commit comments */
+               if (!patchlen && memcmp(line, "diff ", 5))
+                       continue;
+
+               /* Ignore git-diff index header */
+               if (!memcmp(line, "index ", 6))
+                       continue;
+
+               /* Ignore line numbers when computing the SHA1 of the patch */
+               if (!memcmp(line, "@@ -", 4))
+                       continue;
+
+               /* Compute the sha without whitespace */
+               len = remove_space(line);
+               patchlen += len;
+               SHA1_Update(&ctx, line, len);
+       }
+       flush_current_id(patchlen, sha1, &ctx);
+}
+
+static const char patch_id_usage[] = "git-patch-id < patch";
+
+int main(int argc, char **argv)
+{
+       if (argc != 1)
+               usage(patch_id_usage);
+
+       generate_id_list();
+       return 0;
+}      
diff --git a/path.c b/path.c
new file mode 100644 (file)
index 0000000..334b2bd
--- /dev/null
+++ b/path.c
@@ -0,0 +1,252 @@
+/*
+ * I'm tired of doing "vsnprintf()" etc just to open a
+ * file, so here's a "return static buffer with printf"
+ * interface for paths.
+ *
+ * It's obviously not thread-safe. Sue me. But it's quite
+ * useful for doing things like
+ *
+ *   f = open(mkpath("%s/%s.git", base, name), O_RDONLY);
+ *
+ * which is what it's designed for.
+ */
+#include "cache.h"
+#include <pwd.h>
+
+static char pathname[PATH_MAX];
+static char bad_path[] = "/bad-path/";
+
+static char *cleanup_path(char *path)
+{
+       /* Clean it up */
+       if (!memcmp(path, "./", 2)) {
+               path += 2;
+               while (*path == '/')
+                       path++;
+       }
+       return path;
+}
+
+char *mkpath(const char *fmt, ...)
+{
+       va_list args;
+       unsigned len;
+
+       va_start(args, fmt);
+       len = vsnprintf(pathname, PATH_MAX, fmt, args);
+       va_end(args);
+       if (len >= PATH_MAX)
+               return bad_path;
+       return cleanup_path(pathname);
+}
+
+char *git_path(const char *fmt, ...)
+{
+       const char *git_dir = get_git_dir();
+       va_list args;
+       unsigned len;
+
+       len = strlen(git_dir);
+       if (len > PATH_MAX-100)
+               return bad_path;
+       memcpy(pathname, git_dir, len);
+       if (len && git_dir[len-1] != '/')
+               pathname[len++] = '/';
+       va_start(args, fmt);
+       len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+       va_end(args);
+       if (len >= PATH_MAX)
+               return bad_path;
+       return cleanup_path(pathname);
+}
+
+
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
+{
+       char *env, *pch = path;
+
+       if ((env = getenv("TMPDIR")) == NULL) {
+               strcpy(pch, "/tmp/");
+               len -= 5;
+               pch += 5;
+       } else {
+               size_t n = snprintf(pch, len, "%s/", env);
+
+               len -= n;
+               pch += n;
+       }
+
+       safe_strncpy(pch, template, len);
+
+       return mkstemp(path);
+}
+
+
+char *safe_strncpy(char *dest, const char *src, size_t n)
+{
+       strncpy(dest, src, n);
+       dest[n - 1] = '\0';
+
+       return dest;
+}
+
+int validate_symref(const char *path)
+{
+       struct stat st;
+       char *buf, buffer[256];
+       int len, fd;
+
+       if (lstat(path, &st) < 0)
+               return -1;
+
+       /* Make sure it is a "refs/.." symlink */
+       if (S_ISLNK(st.st_mode)) {
+               len = readlink(path, buffer, sizeof(buffer)-1);
+               if (len >= 5 && !memcmp("refs/", buffer, 5))
+                       return 0;
+               return -1;
+       }
+
+       /*
+        * Anything else, just open it and try to see if it is a symbolic ref.
+        */
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       len = read(fd, buffer, sizeof(buffer)-1);
+       close(fd);
+
+       /*
+        * Is it a symbolic ref?
+        */
+       if (len < 4 || memcmp("ref:", buffer, 4))
+               return -1;
+       buf = buffer + 4;
+       len -= 4;
+       while (len && isspace(*buf))
+               buf++, len--;
+       if (len >= 5 && !memcmp("refs/", buf, 5))
+               return 0;
+       return -1;
+}
+
+static char *user_path(char *buf, char *path, int sz)
+{
+       struct passwd *pw;
+       char *slash;
+       int len, baselen;
+
+       if (!path || path[0] != '~')
+               return NULL;
+       path++;
+       slash = strchr(path, '/');
+       if (path[0] == '/' || !path[0]) {
+               pw = getpwuid(getuid());
+       }
+       else {
+               if (slash) {
+                       *slash = 0;
+                       pw = getpwnam(path);
+                       *slash = '/';
+               }
+               else
+                       pw = getpwnam(path);
+       }
+       if (!pw || !pw->pw_dir || sz <= strlen(pw->pw_dir))
+               return NULL;
+       baselen = strlen(pw->pw_dir);
+       memcpy(buf, pw->pw_dir, baselen);
+       while ((1 < baselen) && (buf[baselen-1] == '/')) {
+               buf[baselen-1] = 0;
+               baselen--;
+       }
+       if (slash && slash[1]) {
+               len = strlen(slash);
+               if (sz <= baselen + len)
+                       return NULL;
+               memcpy(buf + baselen, slash, len + 1);
+       }
+       return buf;
+}
+
+/*
+ * First, one directory to try is determined by the following algorithm.
+ *
+ * (0) If "strict" is given, the path is used as given and no DWIM is
+ *     done. Otherwise:
+ * (1) "~/path" to mean path under the running user's home directory;
+ * (2) "~user/path" to mean path under named user's home directory;
+ * (3) "relative/path" to mean cwd relative directory; or
+ * (4) "/absolute/path" to mean absolute directory.
+ *
+ * Unless "strict" is given, we try access() for existence of "%s.git/.git",
+ * "%s/.git", "%s.git", "%s" in this order.  The first one that exists is
+ * what we try.
+ *
+ * Second, we try chdir() to that.  Upon failure, we return NULL.
+ *
+ * Then, we try if the current directory is a valid git repository.
+ * Upon failure, we return NULL.
+ *
+ * If all goes well, we return the directory we used to chdir() (but
+ * before ~user is expanded), avoiding getcwd() resolving symbolic
+ * links.  User relative paths are also returned as they are given,
+ * except DWIM suffixing.
+ */
+char *enter_repo(char *path, int strict)
+{
+       static char used_path[PATH_MAX];
+       static char validated_path[PATH_MAX];
+
+       if (!path)
+               return NULL;
+
+       if (!strict) {
+               static const char *suffix[] = {
+                       ".git/.git", "/.git", ".git", "", NULL,
+               };
+               int len = strlen(path);
+               int i;
+               while ((1 < len) && (path[len-1] == '/')) {
+                       path[len-1] = 0;
+                       len--;
+               }
+               if (PATH_MAX <= len)
+                       return NULL;
+               if (path[0] == '~') {
+                       if (!user_path(used_path, path, PATH_MAX))
+                               return NULL;
+                       strcpy(validated_path, path);
+                       path = used_path;
+               }
+               else if (PATH_MAX - 10 < len)
+                       return NULL;
+               else {
+                       path = strcpy(used_path, path);
+                       strcpy(validated_path, path);
+               }
+               len = strlen(path);
+               for (i = 0; suffix[i]; i++) {
+                       strcpy(path + len, suffix[i]);
+                       if (!access(path, F_OK)) {
+                               strcat(validated_path, suffix[i]);
+                               break;
+                       }
+               }
+               if (!suffix[i] || chdir(path))
+                       return NULL;
+               path = validated_path;
+       }
+       else if (chdir(path))
+               return NULL;
+
+       if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
+           validate_symref("HEAD") == 0) {
+               putenv("GIT_DIR=.");
+               check_repository_format();
+               return path;
+       }
+
+       return NULL;
+}
diff --git a/peek-remote.c b/peek-remote.c
new file mode 100644 (file)
index 0000000..a90cf22
--- /dev/null
@@ -0,0 +1,58 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char peek_remote_usage[] =
+"git-peek-remote [--exec=upload-pack] [host:]directory";
+static const char *exec = "git-upload-pack";
+
+static int peek_remote(int fd[2])
+{
+       struct ref *ref;
+
+       get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       packet_flush(fd[1]);
+
+       while (ref) {
+               printf("%s      %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+               ref = ref->next;
+       }
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret;
+       char *dest = NULL;
+       int fd[2];
+       pid_t pid;
+       int nongit = 0;
+
+       setup_git_directory_gently(&nongit);
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strncmp("--exec=", arg, 7))
+                               exec = arg + 7;
+                       else
+                               usage(peek_remote_usage);
+                       continue;
+               }
+               dest = arg;
+               break;
+       }
+       if (!dest || i != argc - 1)
+               usage(peek_remote_usage);
+
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = peek_remote(fd);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/pkt-line.c b/pkt-line.c
new file mode 100644 (file)
index 0000000..bb3bab0
--- /dev/null
@@ -0,0 +1,112 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+/*
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error). 
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
+ */
+static void safe_write(int fd, const void *buf, unsigned n)
+{
+       while (n) {
+               int ret = xwrite(fd, buf, n);
+               if (ret > 0) {
+                       buf += ret;
+                       n -= ret;
+                       continue;
+               }
+               if (!ret)
+                       die("write error (disk full?)");
+               die("write error (%s)", strerror(errno));
+       }
+}
+
+/*
+ * If we buffered things up above (we don't, but we should),
+ * we'd flush it here
+ */
+void packet_flush(int fd)
+{
+       safe_write(fd, "0000", 4);
+}
+
+#define hex(a) (hexchar[(a) & 15])
+void packet_write(int fd, const char *fmt, ...)
+{
+       static char buffer[1000];
+       static char hexchar[] = "0123456789abcdef";
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args);
+       va_end(args);
+       if (n >= sizeof(buffer)-4)
+               die("protocol error: impossibly long line");
+       n += 4;
+       buffer[0] = hex(n >> 12);
+       buffer[1] = hex(n >> 8);
+       buffer[2] = hex(n >> 4);
+       buffer[3] = hex(n);
+       safe_write(fd, buffer, n);
+}
+
+static void safe_read(int fd, void *buffer, unsigned size)
+{
+       int n = 0;
+
+       while (n < size) {
+               int ret = xread(fd, buffer + n, size - n);
+               if (ret < 0)
+                       die("read error (%s)", strerror(errno));
+               if (!ret)
+                       die("unexpected EOF");
+               n += ret;
+       }
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+       int n;
+       unsigned len;
+       char linelen[4];
+
+       safe_read(fd, linelen, 4);
+
+       len = 0;
+       for (n = 0; n < 4; n++) {
+               unsigned char c = linelen[n];
+               len <<= 4;
+               if (c >= '0' && c <= '9') {
+                       len += c - '0';
+                       continue;
+               }
+               if (c >= 'a' && c <= 'f') {
+                       len += c - 'a' + 10;
+                       continue;
+               }
+               if (c >= 'A' && c <= 'F') {
+                       len += c - 'A' + 10;
+                       continue;
+               }
+               die("protocol error: bad line length character");
+       }
+       if (!len)
+               return 0;
+       len -= 4;
+       if (len >= size)
+               die("protocol error: bad line length %d", len);
+       safe_read(fd, buffer, len);
+       buffer[len] = 0;
+       return len;
+}
diff --git a/pkt-line.h b/pkt-line.h
new file mode 100644 (file)
index 0000000..51d0cbe
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef PKTLINE_H
+#define PKTLINE_H
+
+/*
+ * Silly packetized line writing interface
+ */
+void packet_flush(int fd);
+void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+int packet_read_line(int fd, char *buffer, unsigned size);
+
+#endif
diff --git a/ppc/sha1.c b/ppc/sha1.c
new file mode 100644 (file)
index 0000000..5ba4fc5
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ *
+ * This version assumes we are running on a big-endian machine.
+ * It calls an external sha1_core() to process blocks of 64 bytes.
+ */
+#include <stdio.h>
+#include <string.h>
+#include "sha1.h"
+
+extern void sha1_core(uint32_t *hash, const unsigned char *p,
+                     unsigned int nblocks);
+
+int SHA1_Init(SHA_CTX *c)
+{
+       c->hash[0] = 0x67452301;
+       c->hash[1] = 0xEFCDAB89;
+       c->hash[2] = 0x98BADCFE;
+       c->hash[3] = 0x10325476;
+       c->hash[4] = 0xC3D2E1F0;
+       c->len = 0;
+       c->cnt = 0;
+       return 0;
+}
+
+int SHA1_Update(SHA_CTX *c, const void *ptr, unsigned long n)
+{
+       unsigned long nb;
+       const unsigned char *p = ptr;
+
+       c->len += n << 3;
+       while (n != 0) {
+               if (c->cnt || n < 64) {
+                       nb = 64 - c->cnt;
+                       if (nb > n)
+                               nb = n;
+                       memcpy(&c->buf.b[c->cnt], p, nb);
+                       if ((c->cnt += nb) == 64) {
+                               sha1_core(c->hash, c->buf.b, 1);
+                               c->cnt = 0;
+                       }
+               } else {
+                       nb = n >> 6;
+                       sha1_core(c->hash, p, nb);
+                       nb <<= 6;
+               }
+               n -= nb;
+               p += nb;
+       }
+       return 0;
+}      
+
+int SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+       unsigned int cnt = c->cnt;
+
+       c->buf.b[cnt++] = 0x80;
+       if (cnt > 56) {
+               if (cnt < 64)
+                       memset(&c->buf.b[cnt], 0, 64 - cnt);
+               sha1_core(c->hash, c->buf.b, 1);
+               cnt = 0;
+       }
+       if (cnt < 56)
+               memset(&c->buf.b[cnt], 0, 56 - cnt);
+       c->buf.l[7] = c->len;
+       sha1_core(c->hash, c->buf.b, 1);
+       memcpy(hash, c->hash, 20);
+       return 0;
+}
diff --git a/ppc/sha1.h b/ppc/sha1.h
new file mode 100644 (file)
index 0000000..c3c51aa
--- /dev/null
@@ -0,0 +1,20 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#include <stdint.h>
+
+typedef struct sha_context {
+       uint32_t hash[5];
+       uint32_t cnt;
+       uint64_t len;
+       union {
+               unsigned char b[64];
+               uint64_t l[8];
+       } buf;
+} SHA_CTX;
+
+int SHA1_Init(SHA_CTX *c);
+int SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+int SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/ppc/sha1ppc.S b/ppc/sha1ppc.S
new file mode 100644 (file)
index 0000000..e85611a
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * SHA-1 implementation for PowerPC.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#define FS     80
+
+/*
+ * We roll the registers for T, A, B, C, D, E around on each
+ * iteration; T on iteration t is A on iteration t+1, and so on.
+ * We use registers 7 - 12 for this.
+ */
+#define RT(t)  ((((t)+5)%6)+7)
+#define RA(t)  ((((t)+4)%6)+7)
+#define RB(t)  ((((t)+3)%6)+7)
+#define RC(t)  ((((t)+2)%6)+7)
+#define RD(t)  ((((t)+1)%6)+7)
+#define RE(t)  ((((t)+0)%6)+7)
+
+/* We use registers 16 - 31 for the W values */
+#define W(t)   (((t)%16)+16)
+
+#define STEPD0(t)                              \
+       and     %r6,RB(t),RC(t);                \
+       andc    %r0,RD(t),RB(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       or      %r6,%r6,%r0;                    \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define STEPD1(t)                              \
+       xor     %r6,RB(t),RC(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       xor     %r6,%r6,RD(t);                  \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define STEPD2(t)                              \
+       and     %r6,RB(t),RC(t);                \
+       and     %r0,RB(t),RD(t);                \
+       rotlwi  RT(t),RA(t),5;                  \
+       rotlwi  RB(t),RB(t),30;                 \
+       or      %r6,%r6,%r0;                    \
+       and     %r0,RC(t),RD(t);                \
+       or      %r6,%r6,%r0;                    \
+       add     %r0,RE(t),%r15;                 \
+       add     RT(t),RT(t),%r6;                \
+       add     %r0,%r0,W(t);                   \
+       add     RT(t),RT(t),%r0
+
+#define LOADW(t)                               \
+       lwz     W(t),(t)*4(%r4)
+
+#define UPDATEW(t)                             \
+       xor     %r0,W((t)-3),W((t)-8);          \
+       xor     W(t),W((t)-16),W((t)-14);       \
+       xor     W(t),W(t),%r0;                  \
+       rotlwi  W(t),W(t),1
+
+#define STEP0LD4(t)                            \
+       STEPD0(t);   LOADW((t)+4);              \
+       STEPD0((t)+1); LOADW((t)+5);            \
+       STEPD0((t)+2); LOADW((t)+6);            \
+       STEPD0((t)+3); LOADW((t)+7)
+
+#define STEPUP4(t, fn)                         \
+       STEP##fn(t);   UPDATEW((t)+4);          \
+       STEP##fn((t)+1); UPDATEW((t)+5);        \
+       STEP##fn((t)+2); UPDATEW((t)+6);        \
+       STEP##fn((t)+3); UPDATEW((t)+7)
+
+#define STEPUP20(t, fn)                                \
+       STEPUP4(t, fn);                         \
+       STEPUP4((t)+4, fn);                     \
+       STEPUP4((t)+8, fn);                     \
+       STEPUP4((t)+12, fn);                    \
+       STEPUP4((t)+16, fn)
+
+       .globl  sha1_core
+sha1_core:
+       stwu    %r1,-FS(%r1)
+       stw     %r15,FS-68(%r1)
+       stw     %r16,FS-64(%r1)
+       stw     %r17,FS-60(%r1)
+       stw     %r18,FS-56(%r1)
+       stw     %r19,FS-52(%r1)
+       stw     %r20,FS-48(%r1)
+       stw     %r21,FS-44(%r1)
+       stw     %r22,FS-40(%r1)
+       stw     %r23,FS-36(%r1)
+       stw     %r24,FS-32(%r1)
+       stw     %r25,FS-28(%r1)
+       stw     %r26,FS-24(%r1)
+       stw     %r27,FS-20(%r1)
+       stw     %r28,FS-16(%r1)
+       stw     %r29,FS-12(%r1)
+       stw     %r30,FS-8(%r1)
+       stw     %r31,FS-4(%r1)
+
+       /* Load up A - E */
+       lwz     RA(0),0(%r3)    /* A */
+       lwz     RB(0),4(%r3)    /* B */
+       lwz     RC(0),8(%r3)    /* C */
+       lwz     RD(0),12(%r3)   /* D */
+       lwz     RE(0),16(%r3)   /* E */
+
+       mtctr   %r5
+
+1:     LOADW(0)
+       LOADW(1)
+       LOADW(2)
+       LOADW(3)
+
+       lis     %r15,0x5a82     /* K0-19 */
+       ori     %r15,%r15,0x7999
+       STEP0LD4(0)
+       STEP0LD4(4)
+       STEP0LD4(8)
+       STEPUP4(12, D0)
+       STEPUP4(16, D0)
+
+       lis     %r15,0x6ed9     /* K20-39 */
+       ori     %r15,%r15,0xeba1
+       STEPUP20(20, D1)
+
+       lis     %r15,0x8f1b     /* K40-59 */
+       ori     %r15,%r15,0xbcdc
+       STEPUP20(40, D2)
+
+       lis     %r15,0xca62     /* K60-79 */
+       ori     %r15,%r15,0xc1d6
+       STEPUP4(60, D1)
+       STEPUP4(64, D1)
+       STEPUP4(68, D1)
+       STEPUP4(72, D1)
+       STEPD1(76)
+       STEPD1(77)
+       STEPD1(78)
+       STEPD1(79)
+
+       lwz     %r20,16(%r3)
+       lwz     %r19,12(%r3)
+       lwz     %r18,8(%r3)
+       lwz     %r17,4(%r3)
+       lwz     %r16,0(%r3)
+       add     %r20,RE(80),%r20
+       add     RD(0),RD(80),%r19
+       add     RC(0),RC(80),%r18
+       add     RB(0),RB(80),%r17
+       add     RA(0),RA(80),%r16
+       mr      RE(0),%r20
+       stw     RA(0),0(%r3)
+       stw     RB(0),4(%r3)
+       stw     RC(0),8(%r3)
+       stw     RD(0),12(%r3)
+       stw     RE(0),16(%r3)
+
+       addi    %r4,%r4,64
+       bdnz    1b
+
+       lwz     %r15,FS-68(%r1)
+       lwz     %r16,FS-64(%r1)
+       lwz     %r17,FS-60(%r1)
+       lwz     %r18,FS-56(%r1)
+       lwz     %r19,FS-52(%r1)
+       lwz     %r20,FS-48(%r1)
+       lwz     %r21,FS-44(%r1)
+       lwz     %r22,FS-40(%r1)
+       lwz     %r23,FS-36(%r1)
+       lwz     %r24,FS-32(%r1)
+       lwz     %r25,FS-28(%r1)
+       lwz     %r26,FS-24(%r1)
+       lwz     %r27,FS-20(%r1)
+       lwz     %r28,FS-16(%r1)
+       lwz     %r29,FS-12(%r1)
+       lwz     %r30,FS-8(%r1)
+       lwz     %r31,FS-4(%r1)
+       addi    %r1,%r1,FS
+       blr
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644 (file)
index 0000000..d24b097
--- /dev/null
@@ -0,0 +1,79 @@
+#include "cache.h"
+
+static const char prune_packed_usage[] =
+"git-prune-packed [-n]";
+
+static int dryrun;
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len)
+{
+       struct dirent *de;
+       char hex[40];
+
+       sprintf(hex, "%02x", i);
+       while ((de = readdir(dir)) != NULL) {
+               unsigned char sha1[20];
+               if (strlen(de->d_name) != 38)
+                       continue;
+               memcpy(hex+2, de->d_name, 38);
+               if (get_sha1_hex(hex, sha1))
+                       continue;
+               if (!has_sha1_pack(sha1))
+                       continue;
+               memcpy(pathname + len, de->d_name, 38);
+               if (dryrun)
+                       printf("rm -f %s\n", pathname);
+               else if (unlink(pathname) < 0)
+                       error("unable to unlink %s", pathname);
+       }
+       pathname[len] = 0;
+       rmdir(pathname);
+}
+
+static void prune_packed_objects(void)
+{
+       int i;
+       static char pathname[PATH_MAX];
+       const char *dir = get_object_directory();
+       int len = strlen(dir);
+
+       if (len > PATH_MAX - 42)
+               die("impossible object directory");
+       memcpy(pathname, dir, len);
+       if (len && pathname[len-1] != '/')
+               pathname[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+
+               sprintf(pathname + len, "%02x/", i);
+               d = opendir(pathname);
+               if (!d)
+                       continue;
+               prune_dir(i, d, pathname, len + 3);
+               closedir(d);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       setup_git_directory();
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-n"))
+                               dryrun = 1;
+                       else
+                               usage(prune_packed_usage);
+                       continue;
+               }
+               /* Handle arguments here .. */
+               usage(prune_packed_usage);
+       }
+       sync();
+       prune_packed_objects();
+       return 0;
+}
diff --git a/quote.c b/quote.c
new file mode 100644 (file)
index 0000000..06792d4
--- /dev/null
+++ b/quote.c
@@ -0,0 +1,266 @@
+#include "cache.h"
+#include "quote.h"
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ *
+ * E.g.
+ *  original     sq_quote     result
+ *  name     ==> name      ==> 'name'
+ *  a b      ==> a b       ==> 'a b'
+ *  a'b      ==> a'\''b    ==> 'a'\''b'
+ *  a!b      ==> a'\!'b    ==> 'a'\!'b'
+ */
+#undef EMIT
+#define EMIT(x) ( (++len < n) && (*bp++ = (x)) )
+
+static inline int need_bs_quote(char c)
+{
+       return (c == '\'' || c == '!');
+}
+
+size_t sq_quote_buf(char *dst, size_t n, const char *src)
+{
+       char c;
+       char *bp = dst;
+       size_t len = 0;
+
+       EMIT('\'');
+       while ((c = *src++)) {
+               if (need_bs_quote(c)) {
+                       EMIT('\'');
+                       EMIT('\\');
+                       EMIT(c);
+                       EMIT('\'');
+               } else {
+                       EMIT(c);
+               }
+       }
+       EMIT('\'');
+
+       if ( n )
+               *bp = 0;
+
+       return len;
+}
+
+char *sq_quote(const char *src)
+{
+       char *buf;
+       size_t cnt;
+
+       cnt = sq_quote_buf(NULL, 0, src) + 1;
+       buf = xmalloc(cnt);
+       sq_quote_buf(buf, cnt, src);
+
+       return buf;
+}
+
+char *sq_dequote(char *arg)
+{
+       char *dst = arg;
+       char *src = arg;
+       char c;
+
+       if (*src != '\'')
+               return NULL;
+       for (;;) {
+               c = *++src;
+               if (!c)
+                       return NULL;
+               if (c != '\'') {
+                       *dst++ = c;
+                       continue;
+               }
+               /* We stepped out of sq */
+               switch (*++src) {
+               case '\0':
+                       *dst = 0;
+                       return arg;
+               case '\\':
+                       c = *++src;
+                       if (need_bs_quote(c) && *++src == '\'') {
+                               *dst++ = c;
+                               continue;
+                       }
+               /* Fallthrough */
+               default:
+                       return NULL;
+               }
+       }
+}
+
+/*
+ * C-style name quoting.
+ *
+ * Does one of three things:
+ *
+ * (1) if outbuf and outfp are both NULL, inspect the input name and
+ *     counts the number of bytes that are needed to hold c_style
+ *     quoted version of name, counting the double quotes around
+ *     it but not terminating NUL, and returns it.  However, if name
+ *     does not need c_style quoting, it returns 0.
+ *
+ * (2) if outbuf is not NULL, it must point at a buffer large enough
+ *     to hold the c_style quoted version of name, enclosing double
+ *     quotes, and terminating NUL.  Fills outbuf with c_style quoted
+ *     version of name enclosed in double-quote pair.  Return value
+ *     is undefined.
+ *
+ * (3) if outfp is not NULL, outputs c_style quoted version of name,
+ *     but not enclosed in double-quote pair.  Return value is undefined.
+ */
+
+static int quote_c_style_counted(const char *name, int namelen,
+                                char *outbuf, FILE *outfp, int no_dq)
+{
+#undef EMIT
+#define EMIT(c) \
+       (outbuf ? (*outbuf++ = (c)) : outfp ? fputc(c, outfp) : (count++))
+
+#define EMITQ() EMIT('\\')
+
+       const char *sp;
+       int ch, count = 0, needquote = 0;
+
+       if (!no_dq)
+               EMIT('"');
+       for (sp = name; sp < name + namelen; sp++) {
+               ch = *sp;
+               if (!ch)
+                       break;
+               if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
+                   (ch == 0177)) {
+                       needquote = 1;
+                       switch (ch) {
+                       case '\a': EMITQ(); ch = 'a'; break;
+                       case '\b': EMITQ(); ch = 'b'; break;
+                       case '\f': EMITQ(); ch = 'f'; break;
+                       case '\n': EMITQ(); ch = 'n'; break;
+                       case '\r': EMITQ(); ch = 'r'; break;
+                       case '\t': EMITQ(); ch = 't'; break;
+                       case '\v': EMITQ(); ch = 'v'; break;
+
+                       case '\\': /* fallthru */
+                       case '"': EMITQ(); break;
+                       default:
+                               /* octal */
+                               EMITQ();
+                               EMIT(((ch >> 6) & 03) + '0');
+                               EMIT(((ch >> 3) & 07) + '0');
+                               ch = (ch & 07) + '0';
+                               break;
+                       }
+               }
+               EMIT(ch);
+       }
+       if (!no_dq)
+               EMIT('"');
+       if (outbuf)
+               *outbuf = 0;
+
+       return needquote ? count : 0;
+}
+
+int quote_c_style(const char *name, char *outbuf, FILE *outfp, int no_dq)
+{
+       int cnt = strlen(name);
+       return quote_c_style_counted(name, cnt, outbuf, outfp, no_dq);
+}
+
+/*
+ * C-style name unquoting.
+ *
+ * Quoted should point at the opening double quote.  Returns
+ * an allocated memory that holds unquoted name, which the caller
+ * should free when done.  Updates endp pointer to point at
+ * one past the ending double quote if given.
+ */
+
+char *unquote_c_style(const char *quoted, const char **endp)
+{
+       const char *sp;
+       char *name = NULL, *outp = NULL;
+       int count = 0, ch, ac;
+
+#undef EMIT
+#define EMIT(c) (outp ? (*outp++ = (c)) : (count++))
+
+       if (*quoted++ != '"')
+               return NULL;
+
+       while (1) {
+               /* first pass counts and allocates, second pass fills */
+               for (sp = quoted; (ch = *sp++) != '"'; ) {
+                       if (ch == '\\') {
+                               switch (ch = *sp++) {
+                               case 'a': ch = '\a'; break;
+                               case 'b': ch = '\b'; break;
+                               case 'f': ch = '\f'; break;
+                               case 'n': ch = '\n'; break;
+                               case 'r': ch = '\r'; break;
+                               case 't': ch = '\t'; break;
+                               case 'v': ch = '\v'; break;
+
+                               case '\\': case '"':
+                                       break; /* verbatim */
+
+                               case '0'...'7':
+                                       /* octal */
+                                       ac = ((ch - '0') << 6);
+                                       if ((ch = *sp++) < '0' || '7' < ch)
+                                               return NULL;
+                                       ac |= ((ch - '0') << 3);
+                                       if ((ch = *sp++) < '0' || '7' < ch)
+                                               return NULL;
+                                       ac |= (ch - '0');
+                                       ch = ac;
+                                       break;
+                               default:
+                                       return NULL; /* malformed */
+                               }
+                       }
+                       EMIT(ch);
+               }
+
+               if (name) {
+                       *outp = 0;
+                       if (endp)
+                               *endp = sp;
+                       return name;
+               }
+               outp = name = xmalloc(count + 1);
+       }
+}
+
+void write_name_quoted(const char *prefix, int prefix_len,
+                      const char *name, int quote, FILE *out)
+{
+       int needquote;
+
+       if (!quote) {
+       no_quote:
+               if (prefix_len)
+                       fprintf(out, "%.*s", prefix_len, prefix);
+               fputs(name, out);
+               return;
+       }
+
+       needquote = 0;
+       if (prefix_len)
+               needquote = quote_c_style_counted(prefix, prefix_len,
+                                                 NULL, NULL, 0);
+       if (!needquote)
+               needquote = quote_c_style(name, NULL, NULL, 0);
+       if (needquote) {
+               fputc('"', out);
+               if (prefix_len)
+                       quote_c_style_counted(prefix, prefix_len,
+                                             NULL, out, 1);
+               quote_c_style(name, NULL, out, 1);
+               fputc('"', out);
+       }
+       else
+               goto no_quote;
+}
diff --git a/quote.h b/quote.h
new file mode 100644 (file)
index 0000000..c1ab378
--- /dev/null
+++ b/quote.h
@@ -0,0 +1,47 @@
+#ifndef QUOTE_H
+#define QUOTE_H
+
+#include <stddef.h>
+#include <stdio.h>
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
+ *
+ * For example, if you are passing the result to system() as an
+ * argument:
+ *
+ * sprintf(cmd, "foobar %s %s", sq_quote(arg0), sq_quote(arg1))
+ *
+ * would be appropriate.  If the system() is going to call ssh to
+ * run the command on the other side:
+ *
+ * sprintf(cmd, "git-diff-tree %s %s", sq_quote(arg0), sq_quote(arg1));
+ * sprintf(rcmd, "ssh %s %s", sq_quote(host), sq_quote(cmd));
+ *
+ * Note that the above examples leak memory!  Remember to free result from
+ * sq_quote() in a real application.
+ *
+ * sq_quote_buf() writes to an existing buffer of specified size; it
+ * will return the number of characters that would have been written
+ * excluding the final null regardless of the buffer size.
+ */
+
+extern char *sq_quote(const char *src);
+extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
+
+/* This unwraps what sq_quote() produces in place, but returns
+ * NULL if the input does not look like what sq_quote would have
+ * produced.
+ */
+extern char *sq_dequote(char *);
+
+extern int quote_c_style(const char *name, char *outbuf, FILE *outfp,
+                        int nodq);
+extern char *unquote_c_style(const char *quoted, const char **endp);
+
+extern void write_name_quoted(const char *prefix, int prefix_len,
+                             const char *name, int quote, FILE *out);
+
+#endif
diff --git a/read-cache.c b/read-cache.c
new file mode 100644 (file)
index 0000000..c499c51
--- /dev/null
@@ -0,0 +1,950 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "cache-tree.h"
+
+/* Index extensions.
+ *
+ * The first letter should be 'A'..'Z' for extensions that are not
+ * necessary for a correct operation (i.e. optimization data).
+ * When new extensions are added that _needs_ to be understood in
+ * order to correctly interpret the index file, pick character that
+ * is outside the range, to cause the reader to abort.
+ */
+
+#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
+#define CACHE_EXT_TREE 0x54524545      /* "TREE" */
+
+struct cache_entry **active_cache = NULL;
+static time_t index_file_timestamp;
+unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
+
+struct cache_tree *active_cache_tree = NULL;
+
+/*
+ * This only updates the "non-critical" parts of the directory
+ * cache, ie the parts that aren't tracked by GIT, and only used
+ * to validate the cache.
+ */
+void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
+{
+       ce->ce_ctime.sec = htonl(st->st_ctime);
+       ce->ce_mtime.sec = htonl(st->st_mtime);
+#ifdef USE_NSEC
+       ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec);
+       ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec);
+#endif
+       ce->ce_dev = htonl(st->st_dev);
+       ce->ce_ino = htonl(st->st_ino);
+       ce->ce_uid = htonl(st->st_uid);
+       ce->ce_gid = htonl(st->st_gid);
+       ce->ce_size = htonl(st->st_size);
+
+       if (assume_unchanged)
+               ce->ce_flags |= htons(CE_VALID);
+}
+
+static int ce_compare_data(struct cache_entry *ce, struct stat *st)
+{
+       int match = -1;
+       int fd = open(ce->name, O_RDONLY);
+
+       if (fd >= 0) {
+               unsigned char sha1[20];
+               if (!index_fd(sha1, fd, st, 0, NULL))
+                       match = memcmp(sha1, ce->sha1, 20);
+               close(fd);
+       }
+       return match;
+}
+
+static int ce_compare_link(struct cache_entry *ce, unsigned long expected_size)
+{
+       int match = -1;
+       char *target;
+       void *buffer;
+       unsigned long size;
+       char type[10];
+       int len;
+
+       target = xmalloc(expected_size);
+       len = readlink(ce->name, target, expected_size);
+       if (len != expected_size) {
+               free(target);
+               return -1;
+       }
+       buffer = read_sha1_file(ce->sha1, type, &size);
+       if (!buffer) {
+               free(target);
+               return -1;
+       }
+       if (size == expected_size)
+               match = memcmp(buffer, target, size);
+       free(buffer);
+       free(target);
+       return match;
+}
+
+static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
+{
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               if (ce_compare_data(ce, st))
+                       return DATA_CHANGED;
+               break;
+       case S_IFLNK:
+               if (ce_compare_link(ce, st->st_size))
+                       return DATA_CHANGED;
+               break;
+       default:
+               return TYPE_CHANGED;
+       }
+       return 0;
+}
+
+static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
+{
+       unsigned int changed = 0;
+
+       switch (ntohl(ce->ce_mode) & S_IFMT) {
+       case S_IFREG:
+               changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
+               /* We consider only the owner x bit to be relevant for
+                * "mode changes"
+                */
+               if (trust_executable_bit &&
+                   (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
+                       changed |= MODE_CHANGED;
+               break;
+       case S_IFLNK:
+               changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
+               break;
+       default:
+               die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
+       }
+       if (ce->ce_mtime.sec != htonl(st->st_mtime))
+               changed |= MTIME_CHANGED;
+       if (ce->ce_ctime.sec != htonl(st->st_ctime))
+               changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+       /*
+        * nsec seems unreliable - not all filesystems support it, so
+        * as long as it is in the inode cache you get right nsec
+        * but after it gets flushed, you get zero nsec.
+        */
+       if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec))
+               changed |= MTIME_CHANGED;
+       if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
+               changed |= CTIME_CHANGED;
+#endif 
+
+       if (ce->ce_uid != htonl(st->st_uid) ||
+           ce->ce_gid != htonl(st->st_gid))
+               changed |= OWNER_CHANGED;
+       if (ce->ce_ino != htonl(st->st_ino))
+               changed |= INODE_CHANGED;
+
+#ifdef USE_STDEV
+       /*
+        * st_dev breaks on network filesystems where different
+        * clients will have different views of what "device"
+        * the filesystem is on
+        */
+       if (ce->ce_dev != htonl(st->st_dev))
+               changed |= INODE_CHANGED;
+#endif
+
+       if (ce->ce_size != htonl(st->st_size))
+               changed |= DATA_CHANGED;
+
+       return changed;
+}
+
+int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid)
+{
+       unsigned int changed;
+
+       /*
+        * If it's marked as always valid in the index, it's
+        * valid whatever the checked-out copy says.
+        */
+       if (!ignore_valid && (ce->ce_flags & htons(CE_VALID)))
+               return 0;
+
+       changed = ce_match_stat_basic(ce, st);
+
+       /*
+        * Within 1 second of this sequence:
+        *      echo xyzzy >file && git-update-index --add file
+        * running this command:
+        *      echo frotz >file
+        * would give a falsely clean cache entry.  The mtime and
+        * length match the cache, and other stat fields do not change.
+        *
+        * We could detect this at update-index time (the cache entry
+        * being registered/updated records the same time as "now")
+        * and delay the return from git-update-index, but that would
+        * effectively mean we can make at most one commit per second,
+        * which is not acceptable.  Instead, we check cache entries
+        * whose mtime are the same as the index file timestamp more
+        * carefully than others.
+        */
+       if (!changed &&
+           index_file_timestamp &&
+           index_file_timestamp <= ntohl(ce->ce_mtime.sec))
+               changed |= ce_modified_check_fs(ce, st);
+
+       return changed;
+}
+
+int ce_modified(struct cache_entry *ce, struct stat *st, int really)
+{
+       int changed, changed_fs;
+       changed = ce_match_stat(ce, st, really);
+       if (!changed)
+               return 0;
+       /*
+        * If the mode or type has changed, there's no point in trying
+        * to refresh the entry - it's not going to match
+        */
+       if (changed & (MODE_CHANGED | TYPE_CHANGED))
+               return changed;
+
+       /* Immediately after read-tree or update-index --cacheinfo,
+        * the length field is zero.  For other cases the ce_size
+        * should match the SHA1 recorded in the index entry.
+        */
+       if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
+               return changed;
+
+       changed_fs = ce_modified_check_fs(ce, st);
+       if (changed_fs)
+               return changed | changed_fs;
+       return 0;
+}
+
+int base_name_compare(const char *name1, int len1, int mode1,
+                     const char *name2, int len2, int mode2)
+{
+       unsigned char c1, c2;
+       int len = len1 < len2 ? len1 : len2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp)
+               return cmp;
+       c1 = name1[len];
+       c2 = name2[len];
+       if (!c1 && S_ISDIR(mode1))
+               c1 = '/';
+       if (!c2 && S_ISDIR(mode2))
+               c2 = '/';
+       return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
+{
+       int len1 = flags1 & CE_NAMEMASK;
+       int len2 = flags2 & CE_NAMEMASK;
+       int len = len1 < len2 ? len1 : len2;
+       int cmp;
+
+       cmp = memcmp(name1, name2, len);
+       if (cmp)
+               return cmp;
+       if (len1 < len2)
+               return -1;
+       if (len1 > len2)
+               return 1;
+
+       /* Compare stages  */
+       flags1 &= CE_STAGEMASK;
+       flags2 &= CE_STAGEMASK;
+
+       if (flags1 < flags2)
+               return -1;
+       if (flags1 > flags2)
+               return 1;
+       return 0;
+}
+
+int cache_name_pos(const char *name, int namelen)
+{
+       int first, last;
+
+       first = 0;
+       last = active_nr;
+       while (last > first) {
+               int next = (last + first) >> 1;
+               struct cache_entry *ce = active_cache[next];
+               int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
+               if (!cmp)
+                       return next;
+               if (cmp < 0) {
+                       last = next;
+                       continue;
+               }
+               first = next+1;
+       }
+       return -first-1;
+}
+
+/* Remove entry, return true if there are more entries to go.. */
+int remove_cache_entry_at(int pos)
+{
+       active_cache_changed = 1;
+       active_nr--;
+       if (pos >= active_nr)
+               return 0;
+       memmove(active_cache + pos, active_cache + pos + 1, (active_nr - pos) * sizeof(struct cache_entry *));
+       return 1;
+}
+
+int remove_file_from_cache(const char *path)
+{
+       int pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               pos = -pos-1;
+       while (pos < active_nr && !strcmp(active_cache[pos]->name, path))
+               remove_cache_entry_at(pos);
+       return 0;
+}
+
+int ce_same_name(struct cache_entry *a, struct cache_entry *b)
+{
+       int len = ce_namelen(a);
+       return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
+}
+
+int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+{
+       const char *match, *name;
+       int len;
+
+       if (!pathspec)
+               return 1;
+
+       len = ce_namelen(ce);
+       name = ce->name;
+       while ((match = *pathspec++) != NULL) {
+               int matchlen = strlen(match);
+               if (matchlen > len)
+                       continue;
+               if (memcmp(name, match, matchlen))
+                       continue;
+               if (matchlen && name[matchlen-1] == '/')
+                       return 1;
+               if (name[matchlen] == '/' || !name[matchlen])
+                       return 1;
+               if (!matchlen)
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere, and for obvious reasons don't
+ * want to recurse into ".git" either.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+       /*
+        * The first character was '.', but that
+        * has already been discarded, we now test
+        * the rest.
+        */
+       switch (*rest) {
+       /* "." is not allowed */
+       case '\0': case '/':
+               return 0;
+
+       /*
+        * ".git" followed by  NUL or slash is bad. This
+        * shares the path end test with the ".." case.
+        */
+       case 'g':
+               if (rest[1] != 'i')
+                       break;
+               if (rest[2] != 't')
+                       break;
+               rest += 2;
+       /* fallthrough */
+       case '.':
+               if (rest[1] == '\0' || rest[1] == '/')
+                       return 0;
+       }
+       return 1;
+}
+
+int verify_path(const char *path)
+{
+       char c;
+
+       goto inside;
+       for (;;) {
+               if (!c)
+                       return 1;
+               if (c == '/') {
+inside:
+                       c = *path++;
+                       switch (c) {
+                       default:
+                               continue;
+                       case '/': case '\0':
+                               break;
+                       case '.':
+                               if (verify_dotfile(path))
+                                       continue;
+                       }
+                       return 0;
+               }
+               c = *path++;
+       }
+}
+
+/*
+ * Do we have another file that has the beginning components being a
+ * proper superset of the name we're trying to add?
+ */
+static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       int retval = 0;
+       int len = ce_namelen(ce);
+       int stage = ce_stage(ce);
+       const char *name = ce->name;
+
+       while (pos < active_nr) {
+               struct cache_entry *p = active_cache[pos++];
+
+               if (len >= ce_namelen(p))
+                       break;
+               if (memcmp(name, p->name, len))
+                       break;
+               if (ce_stage(p) != stage)
+                       continue;
+               if (p->name[len] != '/')
+                       continue;
+               retval = -1;
+               if (!ok_to_replace)
+                       break;
+               remove_cache_entry_at(--pos);
+       }
+       return retval;
+}
+
+/*
+ * Do we have another file with a pathname that is a proper
+ * subset of the name we're trying to add?
+ */
+static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       int retval = 0;
+       int stage = ce_stage(ce);
+       const char *name = ce->name;
+       const char *slash = name + ce_namelen(ce);
+
+       for (;;) {
+               int len;
+
+               for (;;) {
+                       if (*--slash == '/')
+                               break;
+                       if (slash <= ce->name)
+                               return retval;
+               }
+               len = slash - name;
+
+               pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
+               if (pos >= 0) {
+                       retval = -1;
+                       if (ok_to_replace)
+                               break;
+                       remove_cache_entry_at(pos);
+                       continue;
+               }
+
+               /*
+                * Trivial optimization: if we find an entry that
+                * already matches the sub-directory, then we know
+                * we're ok, and we can exit.
+                */
+               pos = -pos-1;
+               while (pos < active_nr) {
+                       struct cache_entry *p = active_cache[pos];
+                       if ((ce_namelen(p) <= len) ||
+                           (p->name[len] != '/') ||
+                           memcmp(p->name, name, len))
+                               break; /* not our subdirectory */
+                       if (ce_stage(p) == stage)
+                               /* p is at the same stage as our entry, and
+                                * is a subdirectory of what we are looking
+                                * at, so we cannot have conflicts at our
+                                * level or anything shorter.
+                                */
+                               return retval;
+                       pos++;
+               }
+       }
+       return retval;
+}
+
+/* We may be in a situation where we already have path/file and path
+ * is being added, or we already have path and path/file is being
+ * added.  Either one would result in a nonsense tree that has path
+ * twice when git-write-tree tries to write it out.  Prevent it.
+ * 
+ * If ok-to-replace is specified, we remove the conflicting entries
+ * from the cache so the caller should recompute the insert position.
+ * When this happens, we return non-zero.
+ */
+static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+       /*
+        * We check if the path is a sub-path of a subsequent pathname
+        * first, since removing those will not change the position
+        * in the array
+        */
+       int retval = has_file_name(ce, pos, ok_to_replace);
+       /*
+        * Then check if the path might have a clashing sub-directory
+        * before it.
+        */
+       return retval + has_dir_name(ce, pos, ok_to_replace);
+}
+
+int add_cache_entry(struct cache_entry *ce, int option)
+{
+       int pos;
+       int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
+       int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
+       int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+
+       pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+
+       /* existing match? Just replace it. */
+       if (pos >= 0) {
+               active_cache_changed = 1;
+               active_cache[pos] = ce;
+               return 0;
+       }
+       pos = -pos-1;
+
+       /*
+        * Inserting a merged entry ("stage 0") into the index
+        * will always replace all non-merged entries..
+        */
+       if (pos < active_nr && ce_stage(ce) == 0) {
+               while (ce_same_name(active_cache[pos], ce)) {
+                       ok_to_add = 1;
+                       if (!remove_cache_entry_at(pos))
+                               break;
+               }
+       }
+
+       if (!ok_to_add)
+               return -1;
+       if (!verify_path(ce->name))
+               return -1;
+
+       if (!skip_df_check &&
+           check_file_directory_conflict(ce, pos, ok_to_replace)) {
+               if (!ok_to_replace)
+                       return -1;
+               pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+               pos = -pos-1;
+       }
+
+       /* Make sure the array is big enough .. */
+       if (active_nr == active_alloc) {
+               active_alloc = alloc_nr(active_alloc);
+               active_cache = xrealloc(active_cache, active_alloc * sizeof(struct cache_entry *));
+       }
+
+       /* Add it in.. */
+       active_nr++;
+       if (active_nr > pos)
+               memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
+       active_cache[pos] = ce;
+       active_cache_changed = 1;
+       return 0;
+}
+
+/* Three functions to allow overloaded pointer return; see linux/err.h */
+static inline void *ERR_PTR(long error)
+{
+       return (void *) error;
+}
+
+static inline long PTR_ERR(const void *ptr)
+{
+       return (long) ptr;
+}
+
+static inline long IS_ERR(const void *ptr)
+{
+       return (unsigned long)ptr > (unsigned long)-1000L;
+}
+
+/*
+ * "refresh" does not calculate a new sha1 file or bring the
+ * cache up-to-date for mode/content changes. But what it
+ * _does_ do is to "re-match" the stat information of a file
+ * with the cache, so that you can refresh the cache for a
+ * file that hasn't been changed but where the stat entry is
+ * out of date.
+ *
+ * For example, you'd want to do this after doing a "git-read-tree",
+ * to link up the stat cache details with the proper files.
+ */
+static struct cache_entry *refresh_entry(struct cache_entry *ce, int really)
+{
+       struct stat st;
+       struct cache_entry *updated;
+       int changed, size;
+
+       if (lstat(ce->name, &st) < 0)
+               return ERR_PTR(-errno);
+
+       changed = ce_match_stat(ce, &st, really);
+       if (!changed) {
+               if (really && assume_unchanged &&
+                   !(ce->ce_flags & htons(CE_VALID)))
+                       ; /* mark this one VALID again */
+               else
+                       return NULL;
+       }
+
+       if (ce_modified(ce, &st, really))
+               return ERR_PTR(-EINVAL);
+
+       size = ce_size(ce);
+       updated = xmalloc(size);
+       memcpy(updated, ce, size);
+       fill_stat_cache_info(updated, &st);
+
+       /* In this case, if really is not set, we should leave
+        * CE_VALID bit alone.  Otherwise, paths marked with
+        * --no-assume-unchanged (i.e. things to be edited) will
+        * reacquire CE_VALID bit automatically, which is not
+        * really what we want.
+        */
+       if (!really && assume_unchanged && !(ce->ce_flags & htons(CE_VALID)))
+               updated->ce_flags &= ~htons(CE_VALID);
+
+       return updated;
+}
+
+int refresh_cache(unsigned int flags)
+{
+       int i;
+       int has_errors = 0;
+       int really = (flags & REFRESH_REALLY) != 0;
+       int allow_unmerged = (flags & REFRESH_UNMERGED) != 0;
+       int quiet = (flags & REFRESH_QUIET) != 0;
+       int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce, *new;
+               ce = active_cache[i];
+               if (ce_stage(ce)) {
+                       while ((i < active_nr) &&
+                              ! strcmp(active_cache[i]->name, ce->name))
+                               i++;
+                       i--;
+                       if (allow_unmerged)
+                               continue;
+                       printf("%s: needs merge\n", ce->name);
+                       has_errors = 1;
+                       continue;
+               }
+
+               new = refresh_entry(ce, really);
+               if (!new)
+                       continue;
+               if (IS_ERR(new)) {
+                       if (not_new && PTR_ERR(new) == -ENOENT)
+                               continue;
+                       if (really && PTR_ERR(new) == -EINVAL) {
+                               /* If we are doing --really-refresh that
+                                * means the index is not valid anymore.
+                                */
+                               ce->ce_flags &= ~htons(CE_VALID);
+                               active_cache_changed = 1;
+                       }
+                       if (quiet)
+                               continue;
+                       printf("%s: needs update\n", ce->name);
+                       has_errors = 1;
+                       continue;
+               }
+               active_cache_changed = 1;
+               /* You can NOT just free active_cache[i] here, since it
+                * might not be necessarily malloc()ed but can also come
+                * from mmap(). */
+               active_cache[i] = new;
+       }
+       return has_errors;
+}
+
+static int verify_hdr(struct cache_header *hdr, unsigned long size)
+{
+       SHA_CTX c;
+       unsigned char sha1[20];
+
+       if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
+               return error("bad signature");
+       if (hdr->hdr_version != htonl(2))
+               return error("bad index version");
+       SHA1_Init(&c);
+       SHA1_Update(&c, hdr, size - 20);
+       SHA1_Final(sha1, &c);
+       if (memcmp(sha1, (void *)hdr + size - 20, 20))
+               return error("bad index file sha1 signature");
+       return 0;
+}
+
+static int read_index_extension(const char *ext, void *data, unsigned long sz)
+{
+       switch (CACHE_EXT(ext)) {
+       case CACHE_EXT_TREE:
+               active_cache_tree = cache_tree_read(data, sz);
+               break;
+       default:
+               if (*ext < 'A' || 'Z' < *ext)
+                       return error("index uses %.4s extension, which we do not understand",
+                                    ext);
+               fprintf(stderr, "ignoring %.4s extension\n", ext);
+               break;
+       }
+       return 0;
+}
+
+int read_cache(void)
+{
+       int fd, i;
+       struct stat st;
+       unsigned long size, offset;
+       void *map;
+       struct cache_header *hdr;
+
+       errno = EBUSY;
+       if (active_cache)
+               return active_nr;
+
+       errno = ENOENT;
+       index_file_timestamp = 0;
+       fd = open(get_index_file(), O_RDONLY);
+       if (fd < 0) {
+               if (errno == ENOENT)
+                       return 0;
+               die("index file open failed (%s)", strerror(errno));
+       }
+
+       size = 0; // avoid gcc warning
+       map = MAP_FAILED;
+       if (!fstat(fd, &st)) {
+               size = st.st_size;
+               errno = EINVAL;
+               if (size >= sizeof(struct cache_header) + 20)
+                       map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+       }
+       close(fd);
+       if (map == MAP_FAILED)
+               die("index file mmap failed (%s)", strerror(errno));
+
+       hdr = map;
+       if (verify_hdr(hdr, size) < 0)
+               goto unmap;
+
+       active_nr = ntohl(hdr->hdr_entries);
+       active_alloc = alloc_nr(active_nr);
+       active_cache = xcalloc(active_alloc, sizeof(struct cache_entry *));
+
+       offset = sizeof(*hdr);
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = map + offset;
+               offset = offset + ce_size(ce);
+               active_cache[i] = ce;
+       }
+       index_file_timestamp = st.st_mtime;
+       while (offset <= size - 20 - 8) {
+               /* After an array of active_nr index entries,
+                * there can be arbitrary number of extended
+                * sections, each of which is prefixed with
+                * extension name (4-byte) and section length
+                * in 4-byte network byte order.
+                */
+               unsigned long extsize;
+               memcpy(&extsize, map + offset + 4, 4);
+               extsize = ntohl(extsize);
+               if (read_index_extension(map + offset,
+                                        map + offset + 8, extsize) < 0)
+                       goto unmap;
+               offset += 8;
+               offset += extsize;
+       }
+       return active_nr;
+
+unmap:
+       munmap(map, size);
+       errno = EINVAL;
+       die("index file corrupt");
+}
+
+#define WRITE_BUFFER_SIZE 8192
+static unsigned char write_buffer[WRITE_BUFFER_SIZE];
+static unsigned long write_buffer_len;
+
+static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
+{
+       while (len) {
+               unsigned int buffered = write_buffer_len;
+               unsigned int partial = WRITE_BUFFER_SIZE - buffered;
+               if (partial > len)
+                       partial = len;
+               memcpy(write_buffer + buffered, data, partial);
+               buffered += partial;
+               if (buffered == WRITE_BUFFER_SIZE) {
+                       SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE);
+                       if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE)
+                               return -1;
+                       buffered = 0;
+               }
+               write_buffer_len = buffered;
+               len -= partial;
+               data += partial;
+       }
+       return 0;
+}
+
+static int write_index_ext_header(SHA_CTX *context, int fd,
+                                 unsigned int ext, unsigned int sz)
+{
+       ext = htonl(ext);
+       sz = htonl(sz);
+       if ((ce_write(context, fd, &ext, 4) < 0) ||
+           (ce_write(context, fd, &sz, 4) < 0))
+               return -1;
+       return 0;
+}
+
+static int ce_flush(SHA_CTX *context, int fd)
+{
+       unsigned int left = write_buffer_len;
+
+       if (left) {
+               write_buffer_len = 0;
+               SHA1_Update(context, write_buffer, left);
+       }
+
+       /* Flush first if not enough space for SHA1 signature */
+       if (left + 20 > WRITE_BUFFER_SIZE) {
+               if (write(fd, write_buffer, left) != left)
+                       return -1;
+               left = 0;
+       }
+
+       /* Append the SHA1 signature at the end */
+       SHA1_Final(write_buffer + left, context);
+       left += 20;
+       if (write(fd, write_buffer, left) != left)
+               return -1;
+       return 0;
+}
+
+static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
+{
+       /*
+        * The only thing we care about in this function is to smudge the
+        * falsely clean entry due to touch-update-touch race, so we leave
+        * everything else as they are.  We are called for entries whose
+        * ce_mtime match the index file mtime.
+        */
+       struct stat st;
+
+       if (lstat(ce->name, &st) < 0)
+               return;
+       if (ce_match_stat_basic(ce, &st))
+               return;
+       if (ce_modified_check_fs(ce, &st)) {
+               /* This is "racily clean"; smudge it.  Note that this
+                * is a tricky code.  At first glance, it may appear
+                * that it can break with this sequence:
+                *
+                * $ echo xyzzy >frotz
+                * $ git-update-index --add frotz
+                * $ : >frotz
+                * $ sleep 3
+                * $ echo filfre >nitfol
+                * $ git-update-index --add nitfol
+                *
+                * but it does not.  Whe the second update-index runs,
+                * it notices that the entry "frotz" has the same timestamp
+                * as index, and if we were to smudge it by resetting its
+                * size to zero here, then the object name recorded
+                * in index is the 6-byte file but the cached stat information
+                * becomes zero --- which would then match what we would
+                * obtain from the filesystem next time we stat("frotz"). 
+                *
+                * However, the second update-index, before calling
+                * this function, notices that the cached size is 6
+                * bytes and what is on the filesystem is an empty
+                * file, and never calls us, so the cached size information
+                * for "frotz" stays 6 which does not match the filesystem.
+                */
+               ce->ce_size = htonl(0);
+       }
+}
+
+int write_cache(int newfd, struct cache_entry **cache, int entries)
+{
+       SHA_CTX c;
+       struct cache_header hdr;
+       int i, removed;
+
+       for (i = removed = 0; i < entries; i++)
+               if (!cache[i]->ce_mode)
+                       removed++;
+
+       hdr.hdr_signature = htonl(CACHE_SIGNATURE);
+       hdr.hdr_version = htonl(2);
+       hdr.hdr_entries = htonl(entries - removed);
+
+       SHA1_Init(&c);
+       if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
+               return -1;
+
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               if (!ce->ce_mode)
+                       continue;
+               if (index_file_timestamp &&
+                   index_file_timestamp <= ntohl(ce->ce_mtime.sec))
+                       ce_smudge_racily_clean_entry(ce);
+               if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
+                       return -1;
+       }
+
+       /* Write extension data here */
+       if (active_cache_tree) {
+               unsigned long sz;
+               void *data = cache_tree_write(active_cache_tree, &sz);
+               if (data &&
+                   !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
+                   !ce_write(&c, newfd, data, sz))
+                       ;
+               else {
+                       free(data);
+                       return -1;
+               }
+       }
+       return ce_flush(&c, newfd);
+}
diff --git a/receive-pack.c b/receive-pack.c
new file mode 100644 (file)
index 0000000..93929b5
--- /dev/null
@@ -0,0 +1,340 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include <sys/wait.h>
+
+static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+
+static const char *unpacker[] = { "unpack-objects", NULL };
+
+static int report_status = 0;
+
+static char capabilities[] = "report-status";
+static int capabilities_sent = 0;
+
+static int show_ref(const char *path, const unsigned char *sha1)
+{
+       if (capabilities_sent)
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+       else
+               packet_write(1, "%s %s%c%s\n",
+                            sha1_to_hex(sha1), path, 0, capabilities);
+       capabilities_sent = 1;
+       return 0;
+}
+
+static void write_head_info(void)
+{
+       for_each_ref(show_ref);
+       if (!capabilities_sent)
+               show_ref("capabilities^{}", null_sha1);
+
+}
+
+struct command {
+       struct command *next;
+       const char *error_string;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static struct command *commands = NULL;
+
+static int is_all_zeroes(const char *hex)
+{
+       int i;
+       for (i = 0; i < 40; i++)
+               if (*hex++ != '0')
+                       return 0;
+       return 1;
+}
+
+static int verify_old_ref(const char *name, char *hex_contents)
+{
+       int fd, ret;
+       char buffer[60];
+
+       if (is_all_zeroes(hex_contents))
+               return 0;
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       ret = read(fd, buffer, 40);
+       close(fd);
+       if (ret != 40)
+               return -1;
+       if (memcmp(buffer, hex_contents, 40))
+               return -1;
+       return 0;
+}
+
+static char update_hook[] = "hooks/update";
+
+static int run_update_hook(const char *refname,
+                          char *old_hex, char *new_hex)
+{
+       int code;
+
+       if (access(update_hook, X_OK) < 0)
+               return 0;
+       code = run_command(update_hook, refname, old_hex, new_hex, NULL);
+       switch (code) {
+       case 0:
+               return 0;
+       case -ERR_RUN_COMMAND_FORK:
+               return error("hook fork failed");
+       case -ERR_RUN_COMMAND_EXEC:
+               return error("hook execute failed");
+       case -ERR_RUN_COMMAND_WAITPID:
+               return error("waitpid failed");
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               return error("waitpid is confused");
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               return error("%s died of signal", update_hook);
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               return error("%s died strangely", update_hook);
+       default:
+               error("%s exited with error code %d", update_hook, -code);
+               return -code;
+       }
+}
+
+static int update(struct command *cmd)
+{
+       const char *name = cmd->ref_name;
+       unsigned char *old_sha1 = cmd->old_sha1;
+       unsigned char *new_sha1 = cmd->new_sha1;
+       char new_hex[60], *old_hex, *lock_name;
+       int newfd, namelen, written;
+
+       cmd->error_string = NULL;
+       if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5)) {
+               cmd->error_string = "funny refname";
+               return error("refusing to create funny ref '%s' locally",
+                            name);
+       }
+
+       namelen = strlen(name);
+       lock_name = xmalloc(namelen + 10);
+       memcpy(lock_name, name, namelen);
+       memcpy(lock_name + namelen, ".lock", 6);
+
+       strcpy(new_hex, sha1_to_hex(new_sha1));
+       old_hex = sha1_to_hex(old_sha1);
+       if (!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);
+       }
+       safe_create_leading_directories(lock_name);
+
+       newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (newfd < 0) {
+               cmd->error_string = "can't lock";
+               return error("unable to create %s (%s)",
+                            lock_name, strerror(errno));
+       }
+
+       /* Write the ref with an ending '\n' */
+       new_hex[40] = '\n';
+       new_hex[41] = 0;
+       written = write(newfd, new_hex, 41);
+       /* Remove the '\n' again */
+       new_hex[40] = 0;
+
+       close(newfd);
+       if (written != 41) {
+               unlink(lock_name);
+               cmd->error_string = "can't write";
+               return error("unable to write %s", lock_name);
+       }
+       if (verify_old_ref(name, old_hex) < 0) {
+               unlink(lock_name);
+               cmd->error_string = "raced";
+               return error("%s changed during push", name);
+       }
+       if (run_update_hook(name, old_hex, new_hex)) {
+               unlink(lock_name);
+               cmd->error_string = "hook declined";
+               return error("hook declined to update %s", name);
+       }
+       else if (rename(lock_name, name) < 0) {
+               unlink(lock_name);
+               cmd->error_string = "can't rename";
+               return error("unable to replace %s", name);
+       }
+       else {
+               fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+               return 0;
+       }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *cmd)
+{
+       struct command *cmd_p;
+       int argc;
+       const char **argv;
+
+       if (access(update_post_hook, X_OK) < 0)
+               return;
+       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               if (cmd_p->error_string)
+                       continue;
+               argc++;
+       }
+       argv = xmalloc(sizeof(*argv) * (1 + argc));
+       argv[0] = update_post_hook;
+
+       for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+               char *p;
+               if (cmd_p->error_string)
+                       continue;
+               p = xmalloc(strlen(cmd_p->ref_name) + 1);
+               strcpy(p, cmd_p->ref_name);
+               argv[argc] = p;
+               argc++;
+       }
+       argv[argc] = NULL;
+       run_command_v_opt(argc, argv, RUN_COMMAND_NO_STDIO);
+}
+
+/*
+ * This gets called after(if) we've successfully
+ * unpacked the data payload.
+ */
+static void execute_commands(void)
+{
+       struct command *cmd = commands;
+
+       while (cmd) {
+               update(cmd);
+               cmd = cmd->next;
+       }
+       run_update_post_hook(commands);
+}
+
+static void read_head_info(void)
+{
+       struct command **p = &commands;
+       for (;;) {
+               static char line[1000];
+               unsigned char old_sha1[20], new_sha1[20];
+               struct command *cmd;
+               char *refname;
+               int len, reflen;
+
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       break;
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+               if (len < 83 ||
+                   line[40] != ' ' ||
+                   line[81] != ' ' ||
+                   get_sha1_hex(line, old_sha1) ||
+                   get_sha1_hex(line + 41, new_sha1))
+                       die("protocol error: expected old/new/ref, got '%s'",
+                           line);
+
+               refname = line + 82;
+               reflen = strlen(refname);
+               if (reflen + 82 < len) {
+                       if (strstr(refname + reflen + 1, "report-status"))
+                               report_status = 1;
+               }
+               cmd = xmalloc(sizeof(struct command) + len - 80);
+               memcpy(cmd->old_sha1, old_sha1, 20);
+               memcpy(cmd->new_sha1, new_sha1, 20);
+               memcpy(cmd->ref_name, line + 82, len - 81);
+               cmd->error_string = "n/a (unpacker error)";
+               cmd->next = NULL;
+               *p = cmd;
+               p = &cmd->next;
+       }
+}
+
+static const char *unpack(int *error_code)
+{
+       int code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+
+       *error_code = 0;
+       switch (code) {
+       case 0:
+               return NULL;
+       case -ERR_RUN_COMMAND_FORK:
+               return "unpack fork failed";
+       case -ERR_RUN_COMMAND_EXEC:
+               return "unpack execute failed";
+       case -ERR_RUN_COMMAND_WAITPID:
+               return "waitpid failed";
+       case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               return "waitpid is confused";
+       case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               return "unpacker died of signal";
+       case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+               return "unpacker died strangely";
+       default:
+               *error_code = -code;
+               return "unpacker exited with error code";
+       }
+}
+
+static void report(const char *unpack_status)
+{
+       struct command *cmd;
+       packet_write(1, "unpack %s\n",
+                    unpack_status ? unpack_status : "ok");
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!cmd->error_string)
+                       packet_write(1, "ok %s\n",
+                                    cmd->ref_name);
+               else
+                       packet_write(1, "ng %s %s\n",
+                                    cmd->ref_name, cmd->error_string);
+       }
+       packet_flush(1);
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       char *dir = NULL;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               char *arg = *argv++;
+
+               if (*arg == '-') {
+                       /* Do flag handling here */
+                       usage(receive_pack_usage);
+               }
+               if (dir)
+                       usage(receive_pack_usage);
+               dir = arg;
+       }
+       if (!dir)
+               usage(receive_pack_usage);
+
+       if(!enter_repo(dir, 0))
+               die("'%s': unable to chdir or not a git archive", dir);
+
+       write_head_info();
+
+       /* EOF */
+       packet_flush(1);
+
+       read_head_info();
+       if (commands) {
+               int code;
+               const char *unpack_status = unpack(&code);
+               if (!unpack_status)
+                       execute_commands();
+               if (report_status)
+                       report(unpack_status);
+       }
+       return 0;
+}
diff --git a/refs.c b/refs.c
new file mode 100644 (file)
index 0000000..f91b771
--- /dev/null
+++ b/refs.c
@@ -0,0 +1,515 @@
+#include "refs.h"
+#include "cache.h"
+
+#include <errno.h>
+
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+{
+       int depth = MAXDEPTH, len;
+       char buffer[256];
+
+       for (;;) {
+               struct stat st;
+               char *buf;
+               int fd;
+
+               if (--depth < 0)
+                       return NULL;
+
+               /* Special case: non-existing file.
+                * Not having the refs/heads/new-branch is OK
+                * if we are writing into it, so is .git/HEAD
+                * that points at refs/heads/master still to be
+                * born.  It is NOT OK if we are resolving for
+                * reading.
+                */
+               if (lstat(path, &st) < 0) {
+                       if (reading || errno != ENOENT)
+                               return NULL;
+                       memset(sha1, 0, 20);
+                       return path;
+               }
+
+               /* Follow "normalized" - ie "refs/.." symlinks by hand */
+               if (S_ISLNK(st.st_mode)) {
+                       len = readlink(path, buffer, sizeof(buffer)-1);
+                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
+                               path = git_path("%.*s", len, buffer);
+                               continue;
+                       }
+               }
+
+               /*
+                * Anything else, just open it and try to use it as
+                * a ref
+                */
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return NULL;
+               len = read(fd, buffer, sizeof(buffer)-1);
+               close(fd);
+
+               /*
+                * Is it a symbolic ref?
+                */
+               if (len < 4 || memcmp("ref:", buffer, 4))
+                       break;
+               buf = buffer + 4;
+               len -= 4;
+               while (len && isspace(*buf))
+                       buf++, len--;
+               while (len && isspace(buf[len-1]))
+                       buf[--len] = 0;
+               path = git_path("%.*s", len, buf);
+       }
+       if (len < 40 || get_sha1_hex(buffer, sha1))
+               return NULL;
+       return path;
+}
+
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+
+#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;
+       }
+       return 0;
+}
+
+int read_ref(const char *filename, unsigned char *sha1)
+{
+       if (resolve_ref(filename, sha1, 1))
+               return 0;
+       return -1;
+}
+
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1), int trim)
+{
+       int retval = 0;
+       DIR *dir = opendir(git_path("%s", base));
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *path = xmalloc(baselen + 257);
+
+               if (!strncmp(base, "./", 2)) {
+                       base += 2;
+                       baselen -= 2;
+               }
+               memcpy(path, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       path[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+                       struct stat st;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       if (namelen>5 && !strcmp(de->d_name+namelen-5,".lock"))
+                               continue;
+                       memcpy(path + baselen, de->d_name, namelen+1);
+                       if (stat(git_path("%s", path), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               retval = do_for_each_ref(path, fn, trim);
+                               if (retval)
+                                       break;
+                               continue;
+                       }
+                       if (read_ref(git_path("%s", path), sha1) < 0) {
+                               error("%s points nowhere!", path);
+                               continue;
+                       }
+                       if (!has_sha1_file(sha1)) {
+                               error("%s does not point to a valid "
+                                     "commit object!", path);
+                               continue;
+                       }
+                       retval = fn(path + trim, sha1);
+                       if (retval)
+                               break;
+               }
+               free(path);
+               closedir(dir);
+       }
+       return retval;
+}
+
+int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       unsigned char sha1[20];
+       if (!read_ref(git_path("HEAD"), sha1))
+               return fn("HEAD", sha1);
+       return 0;
+}
+
+int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs", fn, 0);
+}
+
+int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/tags", fn, 10);
+}
+
+int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/heads", fn, 11);
+}
+
+int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref("refs/remotes", fn, 13);
+}
+
+int get_ref_sha1(const char *ref, unsigned char *sha1)
+{
+       if (check_ref_format(ref))
+               return -1;
+       return read_ref(git_path("refs/%s", ref), sha1);
+}
+
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ */
+
+static inline int bad_ref_char(int ch)
+{
+       return (((unsigned) ch) <= ' ' ||
+               ch == '~' || ch == '^' || ch == ':' ||
+               /* 2.13 Pattern Matching Notation */
+               ch == '?' || ch == '*' || ch == '[');
+}
+
+int check_ref_format(const char *ref)
+{
+       int ch, level;
+       const char *cp = ref;
+
+       level = 0;
+       while (1) {
+               while ((ch = *cp++) == '/')
+                       ; /* tolerate duplicated slashes */
+               if (!ch)
+                       return -1; /* should not end with slashes */
+
+               /* we are at the beginning of the path component */
+               if (ch == '.' || bad_ref_char(ch))
+                       return -1;
+
+               /* scan the rest of the path component */
+               while ((ch = *cp++) != 0) {
+                       if (bad_ref_char(ch))
+                               return -1;
+                       if (ch == '/')
+                               break;
+                       if (ch == '.' && *cp == '.')
+                               return -1;
+               }
+               level++;
+               if (!ch) {
+                       if (level < 2)
+                               return -1; /* at least of form "heads/blah" */
+                       return 0;
+               }
+       }
+}
+
+static struct ref_lock *verify_lock(struct ref_lock *lock,
+       const unsigned char *old_sha1, int mustexist)
+{
+       char buf[40];
+       int nr, fd = open(lock->ref_file, O_RDONLY);
+       if (fd < 0 && (mustexist || errno != ENOENT)) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       nr = read(fd, buf, 40);
+       close(fd);
+       if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
+               error("Can't verify ref %s", lock->ref_file);
+               unlock_ref(lock);
+               return NULL;
+       }
+       if (memcmp(lock->old_sha1, old_sha1, 20)) {
+               error("Ref %s is at %s but expected %s", lock->ref_file,
+                       sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
+               unlock_ref(lock);
+               return NULL;
+       }
+       return lock;
+}
+
+static struct ref_lock *lock_ref_sha1_basic(const char *path,
+       int plen,
+       const unsigned char *old_sha1, int mustexist)
+{
+       struct ref_lock *lock;
+       struct stat st;
+
+       lock = xcalloc(1, sizeof(struct ref_lock));
+       lock->lock_fd = -1;
+
+       plen = strlen(path) - plen;
+       path = resolve_ref(path, lock->old_sha1, mustexist);
+       if (!path) {
+               unlock_ref(lock);
+               return NULL;
+       }
+       lock->lk = xcalloc(1, sizeof(struct lock_file));
+
+       lock->ref_file = strdup(path);
+       lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
+       lock->force_write = lstat(lock->ref_file, &st) && errno == ENOENT;
+
+       if (safe_create_leading_directories(lock->ref_file))
+               die("unable to create directory for %s", lock->ref_file);
+       lock->lock_fd = hold_lock_file_for_update(lock->lk, lock->ref_file);
+       if (lock->lock_fd < 0) {
+               error("Couldn't open lock file %s: %s",
+                     lock->lk->filename, strerror(errno));
+               unlock_ref(lock);
+               return NULL;
+       }
+
+       return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+}
+
+struct ref_lock *lock_ref_sha1(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
+{
+       if (check_ref_format(ref))
+               return NULL;
+       return lock_ref_sha1_basic(git_path("refs/%s", ref),
+               5 + strlen(ref), old_sha1, mustexist);
+}
+
+struct ref_lock *lock_any_ref_for_update(const char *ref,
+       const unsigned char *old_sha1, int mustexist)
+{
+       return lock_ref_sha1_basic(git_path("%s", ref),
+               strlen(ref), old_sha1, mustexist);
+}
+
+void unlock_ref(struct ref_lock *lock)
+{
+       if (lock->lock_fd >= 0) {
+               close(lock->lock_fd);
+               /* Do not free lock->lk -- atexit() still looks at them */
+               if (lock->lk)
+                       rollback_lock_file(lock->lk);
+       }
+       if (lock->ref_file)
+               free(lock->ref_file);
+       if (lock->log_file)
+               free(lock->log_file);
+       free(lock);
+}
+
+static int log_ref_write(struct ref_lock *lock,
+       const unsigned char *sha1, const char *msg)
+{
+       int logfd, written, oflags = O_APPEND | O_WRONLY;
+       unsigned maxlen, len;
+       char *logrec;
+       const char *comitter;
+
+       if (log_all_ref_updates) {
+               if (safe_create_leading_directories(lock->log_file) < 0)
+                       return error("unable to create directory for %s",
+                               lock->log_file);
+               oflags |= O_CREAT;
+       }
+
+       logfd = open(lock->log_file, oflags, 0666);
+       if (logfd < 0) {
+               if (!log_all_ref_updates && errno == ENOENT)
+                       return 0;
+               return error("Unable to append to %s: %s",
+                       lock->log_file, strerror(errno));
+       }
+
+       setup_ident();
+       comitter = git_committer_info(1);
+       if (msg) {
+               maxlen = strlen(comitter) + 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),
+                       comitter,
+                       msg);
+       }
+       else {
+               maxlen = strlen(comitter) + 2*40 + 4;
+               logrec = xmalloc(maxlen);
+               len = snprintf(logrec, maxlen, "%s %s %s\n",
+                       sha1_to_hex(lock->old_sha1),
+                       sha1_to_hex(sha1),
+                       comitter);
+       }
+       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+       free(logrec);
+       close(logfd);
+       if (written != len)
+               return error("Unable to append to %s", lock->log_file);
+       return 0;
+}
+
+int write_ref_sha1(struct ref_lock *lock,
+       const unsigned char *sha1, const char *logmsg)
+{
+       static char term = '\n';
+
+       if (!lock)
+               return -1;
+       if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) {
+               unlock_ref(lock);
+               return 0;
+       }
+       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write(lock->lock_fd, &term, 1) != 1
+               || close(lock->lock_fd) < 0) {
+               error("Couldn't write %s", lock->lk->filename);
+               unlock_ref(lock);
+               return -1;
+       }
+       if (log_ref_write(lock, sha1, logmsg) < 0) {
+               unlock_ref(lock);
+               return -1;
+       }
+       if (commit_lock_file(lock->lk)) {
+               error("Couldn't set %s", lock->ref_file);
+               unlock_ref(lock);
+               return -1;
+       }
+       lock->lock_fd = -1;
+       unlock_ref(lock);
+       return 0;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+       const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
+       char *tz_c;
+       int logfd, tz;
+       struct stat st;
+       unsigned long date;
+       unsigned char logged_sha1[20];
+
+       logfile = git_path("logs/%s", ref);
+       logfd = open(logfile, O_RDONLY, 0);
+       if (logfd < 0)
+               die("Unable to read log %s: %s", logfile, strerror(errno));
+       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);
+       close(logfd);
+
+       lastrec = NULL;
+       rec = logend = logdata + st.st_size;
+       while (logdata < rec) {
+               if (logdata < rec && *(rec-1) == '\n')
+                       rec--;
+               lastgt = NULL;
+               while (logdata < rec && *(rec-1) != '\n') {
+                       rec--;
+                       if (*rec == '>')
+                               lastgt = rec;
+               }
+               if (!lastgt)
+                       die("Log %s is corrupt.", logfile);
+               date = strtoul(lastgt + 1, &tz_c, 10);
+               if (date <= at_time) {
+                       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 (memcmp(logged_sha1, sha1, 20)) {
+                                       tz = strtoul(tz_c, NULL, 10);
+                                       fprintf(stderr,
+                                               "warning: Log %s has gap after %s.\n",
+                                               logfile, show_rfc2822_date(date, tz));
+                               }
+                       }
+                       else if (date == at_time) {
+                               if (get_sha1_hex(rec + 41, sha1))
+                                       die("Log %s is corrupt.", logfile);
+                       }
+                       else {
+                               if (get_sha1_hex(rec + 41, logged_sha1))
+                                       die("Log %s is corrupt.", logfile);
+                               if (memcmp(logged_sha1, sha1, 20)) {
+                                       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);
+                       return 0;
+               }
+               lastrec = rec;
+       }
+
+       rec = logdata;
+       while (rec < logend && *rec != '>' && *rec != '\n')
+               rec++;
+       if (rec == logend || *rec == '\n')
+               die("Log %s is corrupt.", logfile);
+       date = strtoul(rec + 1, &tz_c, 10);
+       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;
+}
diff --git a/refs.h b/refs.h
new file mode 100644 (file)
index 0000000..553155c
--- /dev/null
+++ b/refs.h
@@ -0,0 +1,44 @@
+#ifndef REFS_H
+#define REFS_H
+
+struct ref_lock {
+       char *ref_file;
+       char *log_file;
+       struct lock_file *lk;
+       unsigned char old_sha1[20];
+       int lock_fd;
+       int force_write;
+};
+
+/*
+ * Calls the specified function for each ref file until it returns nonzero,
+ * and returns the value
+ */
+extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_tag_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_branch_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_remote_ref(int (*fn)(const char *path, const unsigned char *sha1));
+
+/** Reads the refs file specified into sha1 **/
+extern int get_ref_sha1(const char *ref, unsigned char *sha1);
+
+/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Locks any ref (for 'HEAD' type refs). */
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Release any lock taken but not written. **/
+extern void unlock_ref(struct ref_lock *lock);
+
+/** Writes sha1 into the ref specified by the 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, unsigned char *sha1);
+
+/** Returns 0 if target has the right format for a ref. **/
+extern int check_ref_format(const char *target);
+
+#endif /* REFS_H */
diff --git a/repo-config.c b/repo-config.c
new file mode 100644 (file)
index 0000000..08fc4cc
--- /dev/null
@@ -0,0 +1,177 @@
+#include "cache.h"
+#include <regex.h>
+
+static const char git_config_set_usage[] =
+"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+
+static char* key = NULL;
+static regex_t* key_regexp = NULL;
+static regex_t* regexp = NULL;
+static int show_keys = 0;
+static int use_key_regexp = 0;
+static int do_all = 0;
+static int do_not_match = 0;
+static int seen = 0;
+static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
+
+static int show_all_config(const char *key_, const char *value_)
+{
+       if (value_)
+               printf("%s=%s\n", key_, value_);
+       else
+               printf("%s\n", key_);
+       return 0;
+}
+
+static int show_config(const char* key_, const char* value_)
+{
+       char value[256];
+       const char *vptr = value;
+       int dup_error = 0;
+
+       if (value_ == NULL)
+               value_ = "";
+
+       if (!use_key_regexp && strcmp(key_, key))
+               return 0;
+       if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+               return 0;
+       if (regexp != NULL &&
+                        (do_not_match ^
+                         regexec(regexp, value_, 0, NULL, 0)))
+               return 0;
+
+       if (show_keys)
+               printf("%s ", key_);
+       if (seen && !do_all)
+               dup_error = 1;
+       if (type == T_INT)
+               sprintf(value, "%d", git_config_int(key_, value_));
+       else if (type == T_BOOL)
+               vptr = git_config_bool(key_, value_) ? "true" : "false";
+       else
+               vptr = value_;
+       seen++;
+       if (dup_error) {
+               error("More than one value for the key %s: %s",
+                               key_, vptr);
+       }
+       else
+               printf("%s\n", vptr);
+
+       return 0;
+}
+
+static int get_value(const char* key_, const char* regex_)
+{
+       char *tl;
+
+       key = strdup(key_);
+       for (tl=key+strlen(key)-1; tl >= key && *tl != '.'; --tl)
+               *tl = tolower(*tl);
+       for (tl=key; *tl && *tl != '.'; ++tl)
+               *tl = tolower(*tl);
+
+       if (use_key_regexp) {
+               key_regexp = (regex_t*)malloc(sizeof(regex_t));
+               if (regcomp(key_regexp, key, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid key pattern: %s\n", key_);
+                       return -1;
+               }
+       }
+
+       if (regex_) {
+               if (regex_[0] == '!') {
+                       do_not_match = 1;
+                       regex_++;
+               }
+
+               regexp = (regex_t*)malloc(sizeof(regex_t));
+               if (regcomp(regexp, regex_, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid pattern: %s\n", regex_);
+                       return -1;
+               }
+       }
+
+       git_config(show_config);
+       free(key);
+       if (regexp) {
+               regfree(regexp);
+               free(regexp);
+       }
+
+       if (do_all)
+               return !seen;
+
+       return (seen == 1) ? 0 : 1;
+}
+
+int main(int argc, const char **argv)
+{
+       int nongit = 0;
+       setup_git_directory_gently(&nongit);
+
+       while (1 < argc) {
+               if (!strcmp(argv[1], "--int"))
+                       type = T_INT;
+               else if (!strcmp(argv[1], "--bool"))
+                       type = T_BOOL;
+               else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
+                       return git_config(show_all_config);
+               else
+                       break;
+               argc--;
+               argv++;
+       }
+
+       switch (argc) {
+       case 2:
+               return get_value(argv[1], NULL);
+       case 3:
+               if (!strcmp(argv[1], "--unset"))
+                       return git_config_set(argv[2], NULL);
+               else if (!strcmp(argv[1], "--unset-all"))
+                       return git_config_set_multivar(argv[2], NULL, NULL, 1);
+               else if (!strcmp(argv[1], "--get"))
+                       return get_value(argv[2], NULL);
+               else if (!strcmp(argv[1], "--get-all")) {
+                       do_all = 1;
+                       return get_value(argv[2], NULL);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], NULL);
+               } else
+
+                       return git_config_set(argv[1], argv[2]);
+       case 4:
+               if (!strcmp(argv[1], "--unset"))
+                       return git_config_set_multivar(argv[2], NULL, argv[3], 0);
+               else if (!strcmp(argv[1], "--unset-all"))
+                       return git_config_set_multivar(argv[2], NULL, argv[3], 1);
+               else if (!strcmp(argv[1], "--get"))
+                       return get_value(argv[2], argv[3]);
+               else if (!strcmp(argv[1], "--get-all")) {
+                       do_all = 1;
+                       return get_value(argv[2], argv[3]);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], argv[3]);
+               } else if (!strcmp(argv[1], "--replace-all"))
+
+                       return git_config_set_multivar(argv[2], argv[3], NULL, 1);
+               else
+
+                       return git_config_set_multivar(argv[1], argv[2], argv[3], 0);
+       case 5:
+               if (!strcmp(argv[1], "--replace-all"))
+                       return git_config_set_multivar(argv[2], argv[3], argv[4], 1);
+       case 1:
+       default:
+               usage(git_config_set_usage);
+       }
+       return 0;
+}
diff --git a/revision.c b/revision.c
new file mode 100644 (file)
index 0000000..6a6952c
--- /dev/null
@@ -0,0 +1,998 @@
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "diff.h"
+#include "refs.h"
+#include "revision.h"
+
+static char *path_name(struct name_path *path, const char *name)
+{
+       struct name_path *p;
+       char *n, *m;
+       int nlen = strlen(name);
+       int len = nlen + 1;
+
+       for (p = path; p; p = p->up) {
+               if (p->elem_len)
+                       len += p->elem_len + 1;
+       }
+       n = xmalloc(len);
+       m = n + len - (nlen + 1);
+       strcpy(m, name);
+       for (p = path; p; p = p->up) {
+               if (p->elem_len) {
+                       m -= p->elem_len + 1;
+                       memcpy(m, p->elem, p->elem_len);
+                       m[p->elem_len] = '/';
+               }
+       }
+       return n;
+}
+
+struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name)
+{
+       struct object_list *entry = xmalloc(sizeof(*entry));
+       entry->item = obj;
+       entry->next = *p;
+       entry->name = path_name(path, name);
+       *p = entry;
+       return &entry->next;
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+       if (blob->object.flags & UNINTERESTING)
+               return;
+       blob->object.flags |= UNINTERESTING;
+}
+
+void mark_tree_uninteresting(struct tree *tree)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       struct object *obj = &tree->object;
+
+       if (obj->flags & UNINTERESTING)
+               return;
+       obj->flags |= UNINTERESTING;
+       if (!has_sha1_file(obj->sha1))
+               return;
+       if (parse_tree(tree) < 0)
+               die("bad tree %s", sha1_to_hex(obj->sha1));
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIR(entry.mode))
+                       mark_tree_uninteresting(lookup_tree(entry.sha1));
+               else
+                       mark_blob_uninteresting(lookup_blob(entry.sha1));
+       }
+
+       /*
+        * We don't care about the tree any more
+        * after it has been marked uninteresting.
+        */
+       free(tree->buffer);
+       tree->buffer = NULL;
+}
+
+void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents = commit->parents;
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               if (!(commit->object.flags & UNINTERESTING)) {
+                       commit->object.flags |= UNINTERESTING;
+
+                       /*
+                        * Normally we haven't parsed the parent
+                        * yet, so we won't have a parent of a parent
+                        * here. However, it may turn out that we've
+                        * reached this commit some other way (where it
+                        * wasn't uninteresting), in which case we need
+                        * to mark its parents recursively too..
+                        */
+                       if (commit->parents)
+                               mark_parents_uninteresting(commit);
+               }
+
+               /*
+                * A missing commit is ok iff its parent is marked
+                * uninteresting.
+                *
+                * We just mark such a thing parsed, so that when
+                * it is popped next time around, we won't be trying
+                * to parse it and get an error.
+                */
+               if (!has_sha1_file(commit->object.sha1))
+                       commit->object.parsed = 1;
+               parents = parents->next;
+       }
+}
+
+static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+       add_object(obj, &revs->pending_objects, NULL, name);
+}
+
+static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+{
+       struct object *object;
+
+       object = parse_object(sha1);
+       if (!object)
+               die("bad object %s", name);
+       object->flags |= flags;
+       return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+       unsigned long flags = object->flags;
+
+       /*
+        * Tag object? Look what it points to..
+        */
+       while (object->type == tag_type) {
+               struct tag *tag = (struct tag *) object;
+               if (revs->tag_objects && !(flags & UNINTERESTING))
+                       add_pending_object(revs, object, tag->tag);
+               object = parse_object(tag->tagged->sha1);
+               if (!object)
+                       die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+       }
+
+       /*
+        * Commit object? Just return it, we'll do all the complex
+        * reachability crud.
+        */
+       if (object->type == commit_type) {
+               struct commit *commit = (struct commit *)object;
+               if (parse_commit(commit) < 0)
+                       die("unable to parse commit %s", name);
+               if (flags & UNINTERESTING) {
+                       commit->object.flags |= UNINTERESTING;
+                       mark_parents_uninteresting(commit);
+                       revs->limited = 1;
+               }
+               return commit;
+       }
+
+       /*
+        * Tree object? Either mark it uniniteresting, or add it
+        * to the list of objects to look at later..
+        */
+       if (object->type == tree_type) {
+               struct tree *tree = (struct tree *)object;
+               if (!revs->tree_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_tree_uninteresting(tree);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+
+       /*
+        * Blob object? You know the drill by now..
+        */
+       if (object->type == blob_type) {
+               struct blob *blob = (struct blob *)object;
+               if (!revs->blob_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_blob_uninteresting(blob);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+       die("%s is unknown object", name);
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+       struct commit_list *list = orig;
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return 0;
+       }
+       return 1;
+}
+
+static int tree_difference = REV_TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       int diff = REV_TREE_DIFFERENT;
+
+       /*
+        * Is it an add of a new file? It means that the old tree
+        * didn't have it at all, so we will turn "REV_TREE_SAME" ->
+        * "REV_TREE_NEW", but leave any "REV_TREE_DIFFERENT" alone
+        * (and if it already was "REV_TREE_NEW", we'll keep it
+        * "REV_TREE_NEW" of course).
+        */
+       if (addremove == '+') {
+               diff = tree_difference;
+               if (diff != REV_TREE_SAME)
+                       return;
+               diff = REV_TREE_NEW;
+       }
+       tree_difference = diff;
+}
+
+static void file_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path)
+{
+       tree_difference = REV_TREE_DIFFERENT;
+}
+
+int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
+{
+       if (!t1)
+               return REV_TREE_NEW;
+       if (!t2)
+               return REV_TREE_DIFFERENT;
+       tree_difference = REV_TREE_SAME;
+       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
+                          &revs->pruning) < 0)
+               return REV_TREE_DIFFERENT;
+       return tree_difference;
+}
+
+int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       if (!t1)
+               return 0;
+
+       tree = read_object_with_reference(t1->object.sha1, tree_type, &real.size, NULL);
+       if (!tree)
+               return 0;
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+
+       tree_difference = 0;
+       retval = diff_tree(&empty, &real, "", &revs->pruning);
+       free(tree);
+
+       return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *parent;
+       int tree_changed = 0;
+
+       if (!commit->tree)
+               return;
+
+       if (!commit->parents) {
+               if (!rev_same_tree_as_empty(revs, commit->tree))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
+               struct commit *p = parent->item;
+
+               parse_commit(p);
+               switch (rev_compare_tree(revs, p->tree, commit->tree)) {
+               case REV_TREE_SAME:
+                       if (p->object.flags & UNINTERESTING) {
+                               /* Even if a merge with an uninteresting
+                                * side branch brought the entire change
+                                * we are interested in, we do not want
+                                * to lose the other branches of this
+                                * merge, so we just keep going.
+                                */
+                               pp = &parent->next;
+                               continue;
+                       }
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       return;
+
+               case REV_TREE_NEW:
+                       if (revs->remove_empty_trees &&
+                           rev_same_tree_as_empty(revs, p->tree)) {
+                               /* We are adding all the specified
+                                * paths from this parent, so the
+                                * history beyond this parent is not
+                                * interesting.  Remove its parents
+                                * (they are grandparents for us).
+                                * IOW, we pretend this parent is a
+                                * "root" commit.
+                                */
+                               parse_commit(p);
+                               p->parents = NULL;
+                       }
+               /* fallthrough */
+               case REV_TREE_DIFFERENT:
+                       tree_changed = 1;
+                       pp = &parent->next;
+                       continue;
+               }
+               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+       }
+       if (tree_changed)
+               commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+       struct commit_list *parent = commit->parents;
+
+       if (commit->object.flags & ADDED)
+               return;
+       commit->object.flags |= ADDED;
+
+       /*
+        * If the commit is uninteresting, don't try to
+        * prune parents - we want the maximal uninteresting
+        * set.
+        *
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       if (commit->object.flags & UNINTERESTING) {
+               while (parent) {
+                       struct commit *p = parent->item;
+                       parent = parent->next;
+                       parse_commit(p);
+                       p->object.flags |= UNINTERESTING;
+                       if (p->parents)
+                               mark_parents_uninteresting(p);
+                       if (p->object.flags & SEEN)
+                               continue;
+                       p->object.flags |= SEEN;
+                       insert_by_date(p, list);
+               }
+               return;
+       }
+
+       /*
+        * Ok, the commit wasn't uninteresting. Try to
+        * simplify the commit history and find the parent
+        * that has no differences in the path set if one exists.
+        */
+       if (revs->prune_fn)
+               revs->prune_fn(revs, commit);
+
+       if (revs->no_walk)
+               return;
+
+       parent = commit->parents;
+       while (parent) {
+               struct commit *p = parent->item;
+
+               parent = parent->next;
+
+               parse_commit(p);
+               if (p->object.flags & SEEN)
+                       continue;
+               p->object.flags |= SEEN;
+               insert_by_date(p, list);
+       }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+       struct commit_list *newlist = NULL;
+       struct commit_list **p = &newlist;
+
+       while (list) {
+               struct commit_list *entry = list;
+               struct commit *commit = list->item;
+               struct object *obj = &commit->object;
+
+               list = list->next;
+               free(entry);
+
+               if (revs->max_age != -1 && (commit->date < revs->max_age))
+                       obj->flags |= UNINTERESTING;
+               if (revs->unpacked && has_sha1_pack(obj->sha1))
+                       obj->flags |= UNINTERESTING;
+               add_parents_to_list(revs, commit, &list);
+               if (obj->flags & UNINTERESTING) {
+                       mark_parents_uninteresting(commit);
+                       if (everybody_uninteresting(list))
+                               break;
+                       continue;
+               }
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               p = &commit_list_insert(commit, p)->next;
+       }
+       if (revs->boundary) {
+               /* mark the ones that are on the result list first */
+               for (list = newlist; list; list = list->next) {
+                       struct commit *commit = list->item;
+                       commit->object.flags |= TMP_MARK;
+               }
+               for (list = newlist; list; list = list->next) {
+                       struct commit *commit = list->item;
+                       struct object *obj = &commit->object;
+                       struct commit_list *parent;
+                       if (obj->flags & UNINTERESTING)
+                               continue;
+                       for (parent = commit->parents;
+                            parent;
+                            parent = parent->next) {
+                               struct commit *pcommit = parent->item;
+                               if (!(pcommit->object.flags & UNINTERESTING))
+                                       continue;
+                               pcommit->object.flags |= BOUNDARY;
+                               if (pcommit->object.flags & TMP_MARK)
+                                       continue;
+                               pcommit->object.flags |= TMP_MARK;
+                               p = &commit_list_insert(pcommit, p)->next;
+                       }
+               }
+               for (list = newlist; list; list = list->next) {
+                       struct commit *commit = list->item;
+                       commit->object.flags &= ~TMP_MARK;
+               }
+       }
+       revs->commits = newlist;
+}
+
+static int all_flags;
+static struct rev_info *all_revs;
+
+static int handle_one_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *object = get_reference(all_revs, path, sha1, all_flags);
+       add_pending_object(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);
+}
+
+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+{
+       unsigned char sha1[20];
+       struct object *it;
+       struct commit *commit;
+       struct commit_list *parents;
+
+       if (*arg == '^') {
+               flags ^= UNINTERESTING;
+               arg++;
+       }
+       if (get_sha1(arg, sha1))
+               return 0;
+       while (1) {
+               it = get_reference(revs, arg, sha1, 0);
+               if (strcmp(it->type, tag_type))
+                       break;
+               memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+       }
+       if (strcmp(it->type, commit_type))
+               return 0;
+       commit = (struct commit *)it;
+       for (parents = commit->parents; parents; parents = parents->next) {
+               it = &parents->item->object;
+               it->flags |= flags;
+               add_pending_object(revs, it, arg);
+       }
+       return 1;
+}
+
+void init_revisions(struct rev_info *revs)
+{
+       memset(revs, 0, sizeof(*revs));
+
+       revs->abbrev = DEFAULT_ABBREV;
+       revs->ignore_merges = 1;
+       revs->pruning.recursive = 1;
+       revs->pruning.add_remove = file_add_remove;
+       revs->pruning.change = file_change;
+       revs->lifo = 1;
+       revs->dense = 1;
+       revs->prefix = setup_git_directory();
+       revs->max_age = -1;
+       revs->min_age = -1;
+       revs->max_count = -1;
+
+       revs->prune_fn = NULL;
+       revs->prune_data = NULL;
+
+       revs->topo_setter = topo_sort_default_setter;
+       revs->topo_getter = topo_sort_default_getter;
+
+       revs->commit_format = CMIT_FMT_DEFAULT;
+
+       diff_setup(&revs->diffopt);
+}
+
+/*
+ * Parse revision information, filling in the "rev_info" structure,
+ * and removing the used arguments from the argument list.
+ *
+ * Returns the number of arguments left that weren't recognized
+ * (which are also moved to the head of the argument list)
+ */
+int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
+{
+       int i, flags, seen_dashdash;
+       const char **unrecognized = argv + 1;
+       int left = 1;
+
+       /* First, search for "--" */
+       seen_dashdash = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (strcmp(arg, "--"))
+                       continue;
+               argv[i] = NULL;
+               argc = i;
+               revs->prune_data = get_pathspec(revs->prefix, argv + i + 1);
+               seen_dashdash = 1;
+               break;
+       }
+
+       flags = 0;
+       for (i = 1; i < argc; i++) {
+               struct object *object;
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               char *dotdot;
+               int local_flags;
+
+               if (*arg == '-') {
+                       int opts;
+                       if (!strncmp(arg, "--max-count=", 12)) {
+                               revs->max_count = atoi(arg + 12);
+                               continue;
+                       }
+                       /* accept -<digit>, like traditional "head" */
+                       if ((*arg == '-') && isdigit(arg[1])) {
+                               revs->max_count = atoi(arg + 1);
+                               continue;
+                       }
+                       if (!strcmp(arg, "-n")) {
+                               if (argc <= i + 1)
+                                       die("-n requires an argument");
+                               revs->max_count = atoi(argv[++i]);
+                               continue;
+                       }
+                       if (!strncmp(arg,"-n",2)) {
+                               revs->max_count = atoi(arg + 2);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--max-age=", 10)) {
+                               revs->max_age = atoi(arg + 10);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--since=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--after=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--min-age=", 10)) {
+                               revs->min_age = atoi(arg + 10);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--before=", 9)) {
+                               revs->min_age = approxidate(arg + 9);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--until=", 8)) {
+                               revs->min_age = approxidate(arg + 8);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               handle_all(revs, flags);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--not")) {
+                               flags ^= UNINTERESTING;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               if (++i >= argc)
+                                       die("bad --default argument");
+                               def = argv[i];
+                               continue;
+                       }
+                       if (!strcmp(arg, "--topo-order")) {
+                               revs->topo_order = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--date-order")) {
+                               revs->lifo = 0;
+                               revs->topo_order = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--parents")) {
+                               revs->parents = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--dense")) {
+                               revs->dense = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--sparse")) {
+                               revs->dense = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remove-empty")) {
+                               revs->remove_empty_trees = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-merges")) {
+                               revs->no_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--boundary")) {
+                               revs->boundary = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects-edge")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               revs->edge_hint = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--unpacked")) {
+                               revs->unpacked = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-r")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-t")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               revs->diffopt.tree_in_recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-m")) {
+                               revs->ignore_merges = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-c")) {
+                               revs->diff = 1;
+                               revs->dense_combined_merges = 0;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--cc")) {
+                               revs->diff = 1;
+                               revs->dense_combined_merges = 1;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-v")) {
+                               revs->verbose_header = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--pretty", 8)) {
+                               revs->verbose_header = 1;
+                               revs->commit_format = get_commit_format(arg+8);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--root")) {
+                               revs->show_root_diff = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-commit-id")) {
+                               revs->no_commit_id = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--always")) {
+                               revs->always_show_header = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-abbrev")) {
+                               revs->abbrev = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev")) {
+                               revs->abbrev = DEFAULT_ABBREV;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--abbrev=", 9)) {
+                               revs->abbrev = strtoul(arg + 9, NULL, 10);
+                               if (revs->abbrev < MINIMUM_ABBREV)
+                                       revs->abbrev = MINIMUM_ABBREV;
+                               else if (revs->abbrev > 40)
+                                       revs->abbrev = 40;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev-commit")) {
+                               revs->abbrev_commit = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--full-diff")) {
+                               revs->diff = 1;
+                               revs->full_diff = 1;
+                               continue;
+                       }
+                       opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+                       if (opts > 0) {
+                               revs->diff = 1;
+                               i += opts - 1;
+                               continue;
+                       }
+                       *unrecognized++ = arg;
+                       left++;
+                       continue;
+               }
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char from_sha1[20];
+                       const char *next = dotdot + 2;
+                       const char *this = arg;
+                       *dotdot = 0;
+                       if (!*next)
+                               next = "HEAD";
+                       if (dotdot == arg)
+                               this = "HEAD";
+                       if (!get_sha1(this, from_sha1) &&
+                           !get_sha1(next, sha1)) {
+                               struct object *exclude;
+                               struct object *include;
+
+                               exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
+                               include = get_reference(revs, next, sha1, flags);
+                               if (!exclude || !include)
+                                       die("Invalid revision range %s..%s", arg, next);
+
+                               if (!seen_dashdash) {
+                                       *dotdot = '.';
+                                       verify_non_filename(revs->prefix, arg);
+                               }
+                               add_pending_object(revs, exclude, this);
+                               add_pending_object(revs, include, next);
+                               continue;
+                       }
+                       *dotdot = '.';
+               }
+               dotdot = strstr(arg, "^@");
+               if (dotdot && !dotdot[2]) {
+                       *dotdot = 0;
+                       if (add_parents_only(revs, arg, flags))
+                               continue;
+                       *dotdot = '^';
+               }
+               local_flags = 0;
+               if (*arg == '^') {
+                       local_flags = UNINTERESTING;
+                       arg++;
+               }
+               if (get_sha1(arg, sha1)) {
+                       int j;
+
+                       if (seen_dashdash || local_flags)
+                               die("bad revision '%s'", arg);
+
+                       /* If we didn't have a "--":
+                        * (1) all filenames must exist;
+                        * (2) all rev-args must not be interpretable
+                        *     as a valid filename.
+                        * but the latter we have checked in the main loop.
+                        */
+                       for (j = i; j < argc; j++)
+                               verify_filename(revs->prefix, argv[j]);
+
+                       revs->prune_data = get_pathspec(revs->prefix, argv + i);
+                       break;
+               }
+               if (!seen_dashdash)
+                       verify_non_filename(revs->prefix, arg);
+               object = get_reference(revs, arg, sha1, flags ^ local_flags);
+               add_pending_object(revs, object, arg);
+       }
+       if (def && !revs->pending_objects) {
+               unsigned char sha1[20];
+               struct object *object;
+               if (get_sha1(def, sha1))
+                       die("bad default revision '%s'", def);
+               object = get_reference(revs, def, sha1, 0);
+               add_pending_object(revs, object, def);
+       }
+
+       if (revs->topo_order || revs->unpacked)
+               revs->limited = 1;
+
+       if (revs->prune_data) {
+               diff_tree_setup_paths(revs->prune_data, &revs->pruning);
+               revs->prune_fn = try_to_simplify_commit;
+               if (!revs->full_diff)
+                       diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+       }
+       if (revs->combine_merges) {
+               revs->ignore_merges = 0;
+               if (revs->dense_combined_merges &&
+                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+                       revs->diffopt.output_format = DIFF_FORMAT_PATCH;
+       }
+       revs->diffopt.abbrev = revs->abbrev;
+       diff_setup_done(&revs->diffopt);
+
+       return left;
+}
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+       struct object_list *list;
+
+       list = revs->pending_objects;
+       revs->pending_objects = NULL;
+       while (list) {
+               struct commit *commit = handle_commit(revs, list->item, list->name);
+               if (commit) {
+                       if (!(commit->object.flags & SEEN)) {
+                               commit->object.flags |= SEEN;
+                               insert_by_date(commit, &revs->commits);
+                       }
+               }
+               list = list->next;
+       }
+
+       if (revs->no_walk)
+               return;
+       if (revs->limited)
+               limit_list(revs);
+       if (revs->topo_order)
+               sort_in_topological_order_fn(&revs->commits, revs->lifo,
+                                            revs->topo_setter,
+                                            revs->topo_getter);
+}
+
+static int rewrite_one(struct rev_info *revs, struct commit **pp)
+{
+       for (;;) {
+               struct commit *p = *pp;
+               if (!revs->limited)
+                       add_parents_to_list(revs, p, &revs->commits);
+               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+                       return 0;
+               if (!p->parents)
+                       return -1;
+               *pp = p->parents->item;
+       }
+}
+
+static void rewrite_parents(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp = &commit->parents;
+       while (*pp) {
+               struct commit_list *parent = *pp;
+               if (rewrite_one(revs, &parent->item) < 0) {
+                       *pp = parent->next;
+                       continue;
+               }
+               pp = &parent->next;
+       }
+}
+
+static void mark_boundary_to_show(struct commit *commit)
+{
+       struct commit_list *p = commit->parents;
+       while (p) {
+               commit = p->item;
+               p = p->next;
+               if (commit->object.flags & BOUNDARY)
+                       commit->object.flags |= BOUNDARY_SHOW;
+       }
+}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+
+       if (!list)
+               return NULL;
+
+       /* Check the max_count ... */
+       switch (revs->max_count) {
+       case -1:
+               break;
+       case 0:
+               return NULL;
+       default:
+               revs->max_count--;
+       }
+
+       do {
+               struct commit *commit = revs->commits->item;
+
+               revs->commits = revs->commits->next;
+
+               /*
+                * 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
+                * that we'd otherwise have done in limit_list().
+                */
+               if (!revs->limited) {
+                       if ((revs->unpacked &&
+                            has_sha1_pack(commit->object.sha1)) ||
+                           (revs->max_age != -1 &&
+                            (commit->date < revs->max_age)))
+                               continue;
+                       add_parents_to_list(revs, commit, &revs->commits);
+               }
+               if (commit->object.flags & SHOWN)
+                       continue;
+
+               /* We want to show boundary commits only when their
+                * children are shown.  When path-limiter is in effect,
+                * rewrite_parents() drops some commits from getting shown,
+                * and there is no point showing boundary parents that
+                * are not shown.  After rewrite_parents() rewrites the
+                * parents of a commit that is shown, we mark the boundary
+                * parents with BOUNDARY_SHOW.
+                */
+               if (commit->object.flags & BOUNDARY_SHOW) {
+                       commit->object.flags |= SHOWN;
+                       return commit;
+               }
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               if (revs->no_merges &&
+                   commit->parents && commit->parents->next)
+                       continue;
+               if (revs->prune_fn && revs->dense) {
+                       if (!(commit->object.flags & TREECHANGE))
+                               continue;
+                       if (revs->parents)
+                               rewrite_parents(revs, commit);
+               }
+               if (revs->boundary)
+                       mark_boundary_to_show(commit);
+               commit->object.flags |= SHOWN;
+               return commit;
+       } while (revs->commits);
+       return NULL;
+}
diff --git a/revision.h b/revision.h
new file mode 100644 (file)
index 0000000..7d85b0f
--- /dev/null
@@ -0,0 +1,106 @@
+#ifndef REVISION_H
+#define REVISION_H
+
+#define SEEN           (1u<<0)
+#define UNINTERESTING   (1u<<1)
+#define TREECHANGE     (1u<<2)
+#define SHOWN          (1u<<3)
+#define TMP_MARK       (1u<<4) /* for isolated cases; clean after use */
+#define BOUNDARY       (1u<<5)
+#define BOUNDARY_SHOW  (1u<<6)
+#define ADDED          (1u<<7) /* Parents already parsed and added? */
+
+struct rev_info;
+struct log_info;
+
+typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
+
+struct rev_info {
+       /* Starting list */
+       struct commit_list *commits;
+       struct object_list *pending_objects;
+
+       /* Basic information */
+       const char *prefix;
+       void *prune_data;
+       prune_fn_t *prune_fn;
+
+       /* Traversal flags */
+       unsigned int    dense:1,
+                       no_merges:1,
+                       no_walk:1,
+                       remove_empty_trees:1,
+                       lifo:1,
+                       topo_order:1,
+                       tag_objects:1,
+                       tree_objects:1,
+                       blob_objects:1,
+                       edge_hint:1,
+                       limited:1,
+                       unpacked:1,
+                       boundary:1,
+                       parents:1;
+
+       /* Diff flags */
+       unsigned int    diff:1,
+                       full_diff:1,
+                       show_root_diff:1,
+                       no_commit_id:1,
+                       verbose_header:1,
+                       ignore_merges:1,
+                       combine_merges:1,
+                       dense_combined_merges:1,
+                       always_show_header:1;
+
+       /* Format info */
+       unsigned int    shown_one:1,
+                       abbrev_commit:1;
+       unsigned int    abbrev;
+       enum cmit_fmt   commit_format;
+       struct log_info *loginfo;
+       int             nr, total;
+       const char      *mime_boundary;
+       const char      *add_signoff;
+       const char      *extra_headers;
+
+       /* special limits */
+       int max_count;
+       unsigned long max_age;
+       unsigned long min_age;
+
+       /* diff info for patches and for paths limiting */
+       struct diff_options diffopt;
+       struct diff_options pruning;
+
+       topo_sort_set_fn_t topo_setter;
+       topo_sort_get_fn_t topo_getter;
+};
+
+#define REV_TREE_SAME          0
+#define REV_TREE_NEW           1
+#define REV_TREE_DIFFERENT     2
+
+/* revision.c */
+extern int rev_same_tree_as_empty(struct rev_info *, struct tree *t1);
+extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2);
+
+extern void init_revisions(struct rev_info *revs);
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern void prepare_revision_walk(struct rev_info *revs);
+extern struct commit *get_revision(struct rev_info *revs);
+
+extern void mark_parents_uninteresting(struct commit *commit);
+extern void mark_tree_uninteresting(struct tree *tree);
+
+struct name_path {
+       struct name_path *up;
+       int elem_len;
+       const char *elem;
+};
+
+extern struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name);
+
+#endif
diff --git a/rsh.c b/rsh.c
new file mode 100644 (file)
index 0000000..d665269
--- /dev/null
+++ b/rsh.c
@@ -0,0 +1,112 @@
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "rsh.h"
+#include "quote.h"
+#include "cache.h"
+
+#define COMMAND_SIZE 4096
+
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
+{
+       char *p = *ptrp;
+       int size = *sizep;
+       int oc;
+       int err = 0;
+
+       if ( quote ) {
+               oc = sq_quote_buf(p, size, str);
+       } else {
+               oc = strlen(str);
+               memcpy(p, str, (oc >= size) ? size-1 : oc);
+       }
+
+       if ( oc >= size ) {
+               err = 1;
+               oc = size-1;
+       }
+
+       *ptrp  += oc;
+       **ptrp  = '\0';
+       *sizep -= oc;
+       return err;
+}
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
+                    char *url, int rmt_argc, char **rmt_argv)
+{
+       char *host;
+       char *path;
+       int sv[2];
+       char command[COMMAND_SIZE];
+       char *posn;
+       int sizen;
+       int of;
+       int i;
+
+       if (!strcmp(url, "-")) {
+               *fd_in = 0;
+               *fd_out = 1;
+               return 0;
+       }
+
+       host = strstr(url, "//");
+       if (host) {
+               host += 2;
+               path = strchr(host, '/');
+       } else {
+               host = url;
+               path = strchr(host, ':');
+               if (path)
+                       *(path++) = '\0';
+       }
+       if (!path) {
+               return error("Bad URL: %s", url);
+       }
+       /* $GIT_RSH <host> "env GIT_DIR=<path> <remote_prog> <args...>" */
+       sizen = COMMAND_SIZE;
+       posn = command;
+       of = 0;
+       of |= add_to_string(&posn, &sizen, "env ", 0);
+       of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT "=", 0);
+       of |= add_to_string(&posn, &sizen, path, 1);
+       of |= add_to_string(&posn, &sizen, " ", 0);
+       of |= add_to_string(&posn, &sizen, remote_prog, 1);
+
+       for ( i = 0 ; i < rmt_argc ; i++ ) {
+               of |= add_to_string(&posn, &sizen, " ", 0);
+               of |= add_to_string(&posn, &sizen, rmt_argv[i], 1);
+       }
+
+       of |= add_to_string(&posn, &sizen, " -", 0);
+
+       if ( of )
+               return error("Command line too long");
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv))
+               return error("Couldn't create socket");
+
+       if (!fork()) {
+               const char *ssh, *ssh_basename;
+               ssh = getenv("GIT_SSH");
+               if (!ssh) ssh = "ssh";
+               ssh_basename = strrchr(ssh, '/');
+               if (!ssh_basename)
+                       ssh_basename = ssh;
+               else
+                       ssh_basename++;
+               close(sv[1]);
+               dup2(sv[0], 0);
+               dup2(sv[0], 1);
+               execlp(ssh, ssh_basename, host, command, NULL);
+       }
+       close(sv[0]);
+       *fd_in = sv[1];
+       *fd_out = sv[1];
+       return 0;
+}
diff --git a/rsh.h b/rsh.h
new file mode 100644 (file)
index 0000000..3b41942
--- /dev/null
+++ b/rsh.h
@@ -0,0 +1,7 @@
+#ifndef RSH_H
+#define RSH_H
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
+                    char *url, int rmt_argc, char **rmt_argv);
+
+#endif
diff --git a/run-command.c b/run-command.c
new file mode 100644 (file)
index 0000000..ca67ee9
--- /dev/null
@@ -0,0 +1,74 @@
+#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)
+{
+       pid_t pid = fork();
+
+       if (pid < 0)
+               return -ERR_RUN_COMMAND_FORK;
+       if (!pid) {
+               if (flags & RUN_COMMAND_NO_STDIO) {
+                       int fd = open("/dev/null", O_RDWR);
+                       dup2(fd, 0);
+                       dup2(fd, 1);
+                       close(fd);
+               }
+               if (flags & RUN_GIT_CMD) {
+                       execv_git_cmd(argv);
+               } else {
+                       execvp(argv[0], (char *const*) argv);
+               }
+               die("exec %s failed.", argv[0]);
+       }
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       error("waitpid failed (%s)", strerror(retval));
+                       return -ERR_RUN_COMMAND_WAITPID;
+               }
+               if (retval != pid)
+                       return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
+               if (WIFSIGNALED(status))
+                       return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
+
+               if (!WIFEXITED(status))
+                       return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
+               code = WEXITSTATUS(status);
+               if (code)
+                       return -code;
+               return 0;
+       }
+}
+
+int run_command_v(int argc, const char **argv)
+{
+       return run_command_v_opt(argc, argv, 0);
+}
+
+int run_command(const char *cmd, ...)
+{
+       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) {
+               arg = argv[argc++] = va_arg(param, char *);
+               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);
+}
diff --git a/run-command.h b/run-command.h
new file mode 100644 (file)
index 0000000..70b477a
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef RUN_COMMAND_H
+#define RUN_COMMAND_H
+
+#define MAX_RUN_COMMAND_ARGS 256
+enum {
+       ERR_RUN_COMMAND_FORK = 10000,
+       ERR_RUN_COMMAND_EXEC,
+       ERR_RUN_COMMAND_WAITPID,
+       ERR_RUN_COMMAND_WAITPID_WRONG_PID,
+       ERR_RUN_COMMAND_WAITPID_SIGNAL,
+       ERR_RUN_COMMAND_WAITPID_NOEXIT,
+};
+
+#define RUN_COMMAND_NO_STDIO 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);
+int run_command(const char *cmd, ...);
+
+#endif
diff --git a/send-pack.c b/send-pack.c
new file mode 100644 (file)
index 0000000..409f188
--- /dev/null
@@ -0,0 +1,414 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "pkt-line.h"
+#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";
+static int verbose = 0;
+static int send_all = 0;
+static int force_update = 0;
+static int use_thin_pack = 0;
+
+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",
+               "--stdout",
+               NULL
+       };
+       execv_git_cmd(args);
+       die("git-pack-objects exec failed (%s)", strerror(errno));
+}
+
+static void exec_rev_list(struct ref *refs)
+{
+       struct ref *ref;
+       static const char *args[1000];
+       int i = 0, j;
+
+       args[i++] = "rev-list"; /* 0 */
+       if (use_thin_pack)      /* 1 */
+               args[i++] = "--objects-edge";
+       else
+               args[i++] = "--objects";
+
+       /* First send the ones we care about most */
+       for (ref = refs; ref; ref = ref->next) {
+               if (900 < i)
+                       die("git-rev-list environment overflow");
+               if (!is_zero_sha1(ref->new_sha1)) {
+                       char *buf = malloc(100);
+                       args[i++] = buf;
+                       snprintf(buf, 50, "%s", sha1_to_hex(ref->new_sha1));
+                       buf += 50;
+                       if (!is_zero_sha1(ref->old_sha1) &&
+                           has_sha1_file(ref->old_sha1)) {
+                               args[i++] = buf;
+                               snprintf(buf, 50, "^%s",
+                                        sha1_to_hex(ref->old_sha1));
+                       }
+               }
+       }
+
+       /* Then a handful of the remainder
+        * NEEDSWORK: we would be better off if used the newer ones first.
+        */
+       for (ref = refs, j = i + 16;
+            i < 900 && i < j && ref;
+            ref = ref->next) {
+               if (is_zero_sha1(ref->new_sha1) &&
+                   !is_zero_sha1(ref->old_sha1) &&
+                   has_sha1_file(ref->old_sha1)) {
+                       char *buf = malloc(42);
+                       args[i++] = buf;
+                       snprintf(buf, 42, "^%s", sha1_to_hex(ref->old_sha1));
+               }
+       }
+       args[i] = NULL;
+       execv_git_cmd(args);
+       die("git-rev-list exec failed (%s)", strerror(errno));
+}
+
+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) {
+               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");
+       dup2(pipe_fd[1], 1);
+       close(pipe_fd[0]);
+       close(pipe_fd[1]);
+       close(fd);
+       exec_rev_list(refs);
+}
+
+static int pack_objects(int fd, struct ref *refs)
+{
+       pid_t rev_list_pid;
+
+       rev_list_pid = fork();
+       if (!rev_list_pid) {
+               rev_list(fd, refs);
+               die("rev-list setup failed");
+       }
+       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
+        */
+       return 0;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+                    const unsigned char *old_sha1)
+{
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       old = (struct commit *) o;
+
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != commit_type)
+               return 0;
+       new = (struct commit *) o;
+
+       if (parse_commit(new) < 0)
+               return 0;
+
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, 1);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, 1);
+       unmark_and_free(used, 1);
+       return found;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+       struct ref *ref;
+       int len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       memcpy(ref->new_sha1, sha1, 20);
+       memcpy(ref->name, refname, len);
+       *local_tail = ref;
+       local_tail = &ref->next;
+       return 0;
+}
+
+static void get_local_heads(void)
+{
+       local_tail = &local_refs;
+       for_each_ref(one_local_ref);
+}
+
+static int receive_status(int in)
+{
+       char line[1000];
+       int ret = 0;
+       int len = packet_read_line(in, line, sizeof(line));
+       if (len < 10 || memcmp(line, "unpack ", 7)) {
+               fprintf(stderr, "did not receive status back\n");
+               return -1;
+       }
+       if (memcmp(line, "unpack ok\n", 10)) {
+               fputs(line, stderr);
+               ret = -1;
+       }
+       while (1) {
+               len = packet_read_line(in, line, sizeof(line));
+               if (!len)
+                       break;
+               if (len < 3 ||
+                   (memcmp(line, "ok", 2) && memcmp(line, "ng", 2))) {
+                       fprintf(stderr, "protocol error: %s\n", line);
+                       ret = -1;
+                       break;
+               }
+               if (!memcmp(line, "ok", 2))
+                       continue;
+               fputs(line, stderr);
+               ret = -1;
+       }
+       return ret;
+}
+
+static int send_pack(int in, int out, int nr_refspec, char **refspec)
+{
+       struct ref *ref;
+       int new_refs;
+       int ret = 0;
+       int ask_for_status_report = 0;
+       int expect_status_report = 0;
+
+       /* No funny business with the matcher */
+       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
+       get_local_heads();
+
+       /* Does the other end support the reporting? */
+       if (server_supports("report-status"))
+               ask_for_status_report = 1;
+
+       /* match them up */
+       if (!remote_tail)
+               remote_tail = &remote_refs;
+       if (match_refs(local_refs, remote_refs, &remote_tail,
+                      nr_refspec, refspec, send_all))
+               return -1;
+
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n");
+               return 0;
+       }
+
+       /*
+        * Finally, tell the other end!
+        */
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char old_hex[60], *new_hex;
+               if (!ref->peer_ref)
+                       continue;
+               if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+                       if (verbose)
+                               fprintf(stderr, "'%s': up-to-date\n", ref->name);
+                       continue;
+               }
+
+               /* This part determines what can overwrite what.
+                * The rules are:
+                *
+                * (0) you can always use --force or +A:B notation to
+                *     selectively force individual ref pairs.
+                *
+                * (1) if the old thing does not exist, it is OK.
+                *
+                * (2) if you do not have the old thing, you are not allowed
+                *     to overwrite it; you would not know what you are losing
+                *     otherwise.
+                *
+                * (3) if both new and old are commit-ish, and new is a
+                *     descendant of old, it is OK.
+                */
+
+               if (!force_update &&
+                   !is_zero_sha1(ref->old_sha1) &&
+                   !ref->force) {
+                       if (!has_sha1_file(ref->old_sha1) ||
+                           !ref_newer(ref->peer_ref->new_sha1,
+                                      ref->old_sha1)) {
+                               /* We do not have the remote ref, or
+                                * we know that the remote ref is not
+                                * an ancestor of what we are trying to
+                                * push.  Either way this can be losing
+                                * commits at the remote end and likely
+                                * we were not up to date to begin with.
+                                */
+                               error("remote '%s' is not a strict "
+                                     "subset of local ref '%s'. "
+                                     "maybe you are not up-to-date and "
+                                     "need to pull first?",
+                                     ref->name,
+                                     ref->peer_ref->name);
+                               ret = -2;
+                               continue;
+                       }
+               }
+               memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+               if (is_zero_sha1(ref->new_sha1)) {
+                       error("cannot happen anymore");
+                       ret = -3;
+                       continue;
+               }
+               new_refs++;
+               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+               new_hex = sha1_to_hex(ref->new_sha1);
+
+               if (ask_for_status_report) {
+                       packet_write(out, "%s %s %s%c%s",
+                                    old_hex, new_hex, ref->name, 0,
+                                    "report-status");
+                       ask_for_status_report = 0;
+                       expect_status_report = 1;
+               }
+               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);
+       }
+
+       packet_flush(out);
+       if (new_refs)
+               pack_objects(out, remote_refs);
+       close(out);
+
+       if (expect_status_report) {
+               if (receive_status(in))
+                       ret = -4;
+       }
+
+       if (!new_refs && ret == 0)
+               fprintf(stderr, "Everything up-to-date\n");
+       return ret;
+}
+
+
+int main(int argc, char **argv)
+{
+       int i, nr_heads = 0;
+       char *dest = NULL;
+       char **heads = NULL;
+       int fd[2], ret;
+       pid_t pid;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       argv++;
+       for (i = 1; i < argc; i++, argv++) {
+               char *arg = *argv;
+
+               if (*arg == '-') {
+                       if (!strncmp(arg, "--exec=", 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               send_all = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--force")) {
+                               force_update = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--verbose")) {
+                               verbose = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--thin")) {
+                               use_thin_pack = 1;
+                               continue;
+                       }
+                       usage(send_pack_usage);
+               }
+               if (!dest) {
+                       dest = arg;
+                       continue;
+               }
+               heads = argv;
+               nr_heads = argc - i;
+               break;
+       }
+       if (!dest)
+               usage(send_pack_usage);
+       if (heads && send_all)
+               usage(send_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = send_pack(fd[0], fd[1], nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
diff --git a/server-info.c b/server-info.c
new file mode 100644 (file)
index 0000000..05bce7d
--- /dev/null
@@ -0,0 +1,248 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+
+/* refs */
+static FILE *info_ref_fp;
+
+static int add_info_ref(const char *path, const unsigned char *sha1)
+{
+       struct object *o = parse_object(sha1);
+
+       fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
+       if (o->type == tag_type) {
+               o = deref_tag(o, path, 0);
+               if (o)
+                       fprintf(info_ref_fp, "%s        %s^{}\n",
+                               sha1_to_hex(o->sha1), path);
+       }
+       return 0;
+}
+
+static int update_info_refs(int force)
+{
+       char *path0 = strdup(git_path("info/refs"));
+       int len = strlen(path0);
+       char *path1 = xmalloc(len + 2);
+
+       strcpy(path1, path0);
+       strcpy(path1 + len, "+");
+
+       safe_create_leading_directories(path0);
+       info_ref_fp = fopen(path1, "w");
+       if (!info_ref_fp)
+               return error("unable to update %s", path0);
+       for_each_ref(add_info_ref);
+       fclose(info_ref_fp);
+       rename(path1, path0);
+       free(path0);
+       free(path1);
+       return 0;
+}
+
+/* packs */
+static struct pack_info {
+       struct packed_git *p;
+       int old_num;
+       int new_num;
+       int nr_alloc;
+       int nr_heads;
+       unsigned char (*head)[20];
+} **info;
+static int num_pack;
+static const char *objdir;
+static int objdirlen;
+
+static struct pack_info *find_pack_by_name(const char *name)
+{
+       int i;
+       for (i = 0; i < num_pack; i++) {
+               struct packed_git *p = info[i]->p;
+               /* skip "/pack/" after ".git/objects" */
+               if (!strcmp(p->pack_name + objdirlen + 6, name))
+                       return info[i];
+       }
+       return NULL;
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_pack_def(const char *line, int old_cnt)
+{
+       struct pack_info *i = find_pack_by_name(line + 2);
+       if (i) {
+               i->old_num = old_cnt;
+               return 0;
+       }
+       else {
+               /* The file describes a pack that is no longer here */
+               return 1;
+       }
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int read_pack_info_file(const char *infofile)
+{
+       FILE *fp;
+       char line[1000];
+       int old_cnt = 0;
+
+       fp = fopen(infofile, "r");
+       if (!fp)
+               return 1; /* nonexisting is not an error. */
+
+       while (fgets(line, sizeof(line), fp)) {
+               int len = strlen(line);
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+
+               if (!len)
+                       continue;
+
+               switch (line[0]) {
+               case 'P': /* P name */
+                       if (parse_pack_def(line, old_cnt++))
+                               goto out_stale;
+                       break;
+               case 'D': /* we used to emit D but that was misguided. */
+                       goto out_stale;
+                       break;
+               case 'T': /* we used to emit T but nobody uses it. */
+                       goto out_stale;
+                       break;
+               default:
+                       error("unrecognized: %s", line);
+                       break;
+               }
+       }
+       fclose(fp);
+       return 0;
+ out_stale:
+       fclose(fp);
+       return 1;
+}
+
+static int compare_info(const void *a_, const void *b_)
+{
+       struct pack_info * const* a = a_;
+       struct pack_info * const* b = b_;
+
+       if (0 <= (*a)->old_num && 0 <= (*b)->old_num)
+               /* Keep the order in the original */
+               return (*a)->old_num - (*b)->old_num;
+       else if (0 <= (*a)->old_num)
+               /* Only A existed in the original so B is obviously newer */
+               return -1;
+       else if (0 <= (*b)->old_num)
+               /* The other way around. */
+               return 1;
+
+       /* then it does not matter but at least keep the comparison stable */
+       if ((*a)->p == (*b)->p)
+               return 0;
+       else if ((*a)->p < (*b)->p)
+               return -1;
+       else
+               return 1;
+}
+
+static void init_pack_info(const char *infofile, int force)
+{
+       struct packed_git *p;
+       int stale;
+       int i = 0;
+
+       objdir = get_object_directory();
+       objdirlen = strlen(objdir);
+
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next) {
+               /* we ignore things on alternate path since they are
+                * not available to the pullers in general.
+                */
+               if (!p->pack_local)
+                       continue;
+               i++;
+       }
+       num_pack = i;
+       info = xcalloc(num_pack, sizeof(struct pack_info *));
+       for (i = 0, p = packed_git; p; p = p->next) {
+               if (!p->pack_local)
+                       continue;
+               info[i] = xcalloc(1, sizeof(struct pack_info));
+               info[i]->p = p;
+               info[i]->old_num = -1;
+               i++;
+       }
+
+       if (infofile && !force)
+               stale = read_pack_info_file(infofile);
+       else
+               stale = 1;
+
+       for (i = 0; i < num_pack; i++) {
+               if (stale) {
+                       info[i]->old_num = -1;
+                       info[i]->nr_heads = 0;
+               }
+       }
+
+       /* renumber them */
+       qsort(info, num_pack, sizeof(info[0]), compare_info);
+       for (i = 0; i < num_pack; i++)
+               info[i]->new_num = i;
+}
+
+static void write_pack_info_file(FILE *fp)
+{
+       int i;
+       for (i = 0; i < num_pack; i++)
+               fprintf(fp, "P %s\n", info[i]->p->pack_name + objdirlen + 6);
+       fputc('\n', fp);
+}
+
+static int update_info_packs(int force)
+{
+       char infofile[PATH_MAX];
+       char name[PATH_MAX];
+       int namelen;
+       FILE *fp;
+
+       namelen = sprintf(infofile, "%s/info/packs", get_object_directory());
+       strcpy(name, infofile);
+       strcpy(name + namelen, "+");
+
+       init_pack_info(infofile, force);
+
+       safe_create_leading_directories(name);
+       fp = fopen(name, "w");
+       if (!fp)
+               return error("cannot open %s", name);
+       write_pack_info_file(fp);
+       fclose(fp);
+       rename(name, infofile);
+       return 0;
+}
+
+/* public */
+int update_server_info(int force)
+{
+       /* We would add more dumb-server support files later,
+        * including index of available pack files and their
+        * intended audiences.
+        */
+       int errs = 0;
+
+       errs = errs | update_info_refs(force);
+       errs = errs | update_info_packs(force);
+
+       /* remove leftover rev-cache file if there is any */
+       unlink(git_path("info/rev-cache"));
+
+       return errs;
+}
diff --git a/setup.c b/setup.c
new file mode 100644 (file)
index 0000000..fe7f884
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,245 @@
+#include "cache.h"
+
+const char *prefix_path(const char *prefix, int len, const char *path)
+{
+       const char *orig = path;
+       for (;;) {
+               char c;
+               if (*path != '.')
+                       break;
+               c = path[1];
+               /* "." */
+               if (!c) {
+                       path++;
+                       break;
+               }
+               /* "./" */
+               if (c == '/') {
+                       path += 2;
+                       continue;
+               }
+               if (c != '.')
+                       break;
+               c = path[2];
+               if (!c)
+                       path += 2;
+               else if (c == '/')
+                       path += 3;
+               else
+                       break;
+               /* ".." and "../" */
+               /* Remove last component of the prefix */
+               do {
+                       if (!len)
+                               die("'%s' is outside repository", orig);
+                       len--;
+               } while (len && prefix[len-1] != '/');
+               continue;
+       }
+       if (len) {
+               int speclen = strlen(path);
+               char *n = xmalloc(speclen + len + 1);
+       
+               memcpy(n, prefix, len);
+               memcpy(n + len, path, speclen+1);
+               path = n;
+       }
+       return path;
+}
+
+/* 
+ * Unlike prefix_path, this should be used if the named file does
+ * not have to interact with index entry; i.e. name of a random file
+ * on the filesystem.
+ */
+const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
+{
+       static char path[PATH_MAX];
+       if (!pfx || !*pfx || arg[0] == '/')
+               return arg;
+       memcpy(path, pfx, pfx_len);
+       strcpy(path + pfx_len, arg);
+       return path;
+}
+
+/*
+ * Verify a filename that we got as an argument for a pathspec
+ * entry. Note that a filename that begins with "-" never verifies
+ * as true, because even if such a filename were to exist, we want
+ * it to be preceded by the "--" marker (or we want the user to
+ * use a format like "./-filename")
+ */
+void verify_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               die("bad flag '%s' used after filename", arg);
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               return;
+       if (errno == ENOENT)
+               die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+                   "Use '--' to separate paths from revisions", arg);
+       die("'%s': %s", arg, strerror(errno));
+}
+
+/*
+ * Opposite of the above: the command line did not have -- marker
+ * and we parsed the arg as a refname.  It should not be interpretable
+ * as a filename.
+ */
+void verify_non_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               return; /* flag */
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               die("ambiguous argument '%s': both revision and filename\n"
+                   "Use '--' to separate filenames from revisions", arg);
+       if (errno != ENOENT)
+               die("'%s': %s", arg, strerror(errno));
+}
+
+const char **get_pathspec(const char *prefix, const char **pathspec)
+{
+       const char *entry = *pathspec;
+       const char **p;
+       int prefixlen;
+
+       if (!prefix && !entry)
+               return NULL;
+
+       if (!entry) {
+               static const char *spec[2];
+               spec[0] = prefix;
+               spec[1] = NULL;
+               return spec;
+       }
+
+       /* Otherwise we have to re-write the entries.. */
+       p = pathspec;
+       prefixlen = prefix ? strlen(prefix) : 0;
+       do {
+               *p = prefix_path(prefix, prefixlen, entry);
+       } while ((entry = *++p) != NULL);
+       return (const char **) pathspec;
+}
+
+/*
+ * Test if it looks like we're at the top level git directory.
+ * We want to see:
+ *
+ *  - either a .git/objects/ directory _or_ the proper
+ *    GIT_OBJECT_DIRECTORY environment variable
+ *  - a refs/ directory under ".git"
+ *  - either a HEAD symlink or a HEAD file that is formatted as
+ *    a proper "ref:".
+ */
+static int is_toplevel_directory(void)
+{
+       if (access(".git/refs/", X_OK) ||
+           access(getenv(DB_ENVIRONMENT) ?
+                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+           validate_symref(".git/HEAD"))
+               return 0;
+       return 1;
+}
+
+const char *setup_git_directory_gently(int *nongit_ok)
+{
+       static char cwd[PATH_MAX+1];
+       int len, offset;
+
+       /*
+        * If GIT_DIR is set explicitly, we're not going
+        * 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)
+                       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:
+               path[len] = 0;
+               die("Not a git repository: '%s'", path);
+       }
+
+       if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/')
+               die("Unable to read current working directory");
+
+       offset = len = strlen(cwd);
+       for (;;) {
+               if (is_toplevel_directory())
+                       break;
+               chdir("..");
+               do {
+                       if (!offset) {
+                               if (nongit_ok) {
+                                       if (chdir(cwd))
+                                               die("Cannot come back to cwd");
+                                       *nongit_ok = 1;
+                                       return NULL;
+                               }
+                               die("Not a git repository");
+                       }
+               } while (cwd[--offset] != '/');
+       }
+
+       if (offset == len)
+               return NULL;
+
+       /* Make "offset" point to past the '/', and add a '/' at the end */
+       offset++;
+       cwd[len++] = '/';
+       cwd[len] = 0;
+       return cwd + offset;
+}
+
+int check_repository_format_version(const char *var, const char *value)
+{
+       if (strcmp(var, "core.repositoryformatversion") == 0)
+               repository_format_version = git_config_int(var, value);
+       else if (strcmp(var, "core.sharedrepository") == 0)
+               shared_repository = git_config_bool(var, value);
+       return 0;
+}
+
+int check_repository_format(void)
+{
+       git_config(check_repository_format_version);
+       if (GIT_REPO_VERSION < repository_format_version)
+               die ("Expected git repo version <= %d, found %d",
+                    GIT_REPO_VERSION, repository_format_version);
+       return 0;
+}
+
+const char *setup_git_directory(void)
+{
+       const char *retval = setup_git_directory_gently(NULL);
+       check_repository_format();
+       return retval;
+}
diff --git a/sha1_file.c b/sha1_file.c
new file mode 100644 (file)
index 0000000..aea0f40
--- /dev/null
@@ -0,0 +1,1808 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This handles basic git sha1 object files - packing, unpacking,
+ * creation etc.
+ */
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+
+#ifndef O_NOATIME
+#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
+#define O_NOATIME 01000000
+#else
+#define O_NOATIME 0
+#endif
+#endif
+
+const unsigned char null_sha1[20] = { 0, };
+
+static unsigned int sha1_file_open_flag = O_NOATIME;
+
+static unsigned hexval(char c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       return ~0;
+}
+
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+       int i;
+       for (i = 0; i < 20; i++) {
+               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               if (val & ~0xff)
+                       return -1;
+               *sha1++ = val;
+               hex += 2;
+       }
+       return 0;
+}
+
+int adjust_shared_perm(const char *path)
+{
+       struct stat st;
+       int mode;
+
+       if (!shared_repository)
+               return 0;
+       if (lstat(path, &st) < 0)
+               return -1;
+       mode = st.st_mode;
+       if (mode & S_IRUSR)
+               mode |= S_IRGRP;
+       if (mode & S_IWUSR)
+               mode |= S_IWGRP;
+       if (mode & S_IXUSR)
+               mode |= S_IXGRP;
+       if (S_ISDIR(mode))
+               mode |= S_ISGID;
+       if (chmod(path, mode) < 0)
+               return -2;
+       return 0;
+}
+
+int safe_create_leading_directories(char *path)
+{
+       char *pos = path;
+       struct stat st;
+
+       if (*pos == '/')
+               pos++;
+
+       while (pos) {
+               pos = strchr(pos, '/');
+               if (!pos)
+                       break;
+               *pos = 0;
+               if (!stat(path, &st)) {
+                       /* path exists */
+                       if (!S_ISDIR(st.st_mode)) {
+                               *pos = '/';
+                               return -3;
+                       }
+               }
+               else if (mkdir(path, 0777)) {
+                       *pos = '/';
+                       return -1;
+               }
+               else if (adjust_shared_perm(path)) {
+                       *pos = '/';
+                       return -2;
+               }
+               *pos++ = '/';
+       }
+       return 0;
+}
+
+char * sha1_to_hex(const unsigned char *sha1)
+{
+       static int bufno;
+       static char hexbuffer[4][50];
+       static const char hex[] = "0123456789abcdef";
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
+       int i;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       *buf = '\0';
+
+       return buffer;
+}
+
+static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
+{
+       int i;
+       for (i = 0; i < 20; i++) {
+               static char hex[] = "0123456789abcdef";
+               unsigned int val = sha1[i];
+               char *pos = pathbuf + i*2 + (i > 0);
+               *pos++ = hex[val >> 4];
+               *pos = hex[val & 0xf];
+       }
+}
+
+/*
+ * NOTE! This returns a statically allocated buffer, so you have to be
+ * careful about using it. Do a "strdup()" if you need to save the
+ * filename.
+ *
+ * Also note that this returns the location for creating.  Reading
+ * SHA1 file can happen from any alternate directory listed in the
+ * DB_ENVIRONMENT environment variable if it is not found in
+ * the primary object database.
+ */
+char *sha1_file_name(const unsigned char *sha1)
+{
+       static char *name, *base;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               memcpy(base, sha1_file_directory, len);
+               memset(base+len, 0, 60);
+               base[len] = '/';
+               base[len+3] = '/';
+               name = base + len + 1;
+       }
+       fill_sha1_path(name, sha1);
+       return base;
+}
+
+char *sha1_pack_name(const unsigned char *sha1)
+{
+       static const char hex[] = "0123456789abcdef";
+       static char *name, *base, *buf;
+       int i;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+               name = base + len + 11;
+       }
+
+       buf = name;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       
+       return base;
+}
+
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+       static const char hex[] = "0123456789abcdef";
+       static char *name, *base, *buf;
+       int i;
+
+       if (!base) {
+               const char *sha1_file_directory = get_object_directory();
+               int len = strlen(sha1_file_directory);
+               base = xmalloc(len + 60);
+               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
+               name = base + len + 11;
+       }
+
+       buf = name;
+
+       for (i = 0; i < 20; i++) {
+               unsigned int val = *sha1++;
+               *buf++ = hex[val >> 4];
+               *buf++ = hex[val & 0xf];
+       }
+       
+       return base;
+}
+
+struct alternate_object_database *alt_odb_list;
+static struct alternate_object_database **alt_odb_tail;
+
+static void read_info_alternates(const char * alternates, int depth);
+
+/*
+ * Prepare alternate object database registry.
+ *
+ * The variable alt_odb_list points at the list of struct
+ * alternate_object_database.  The elements on this list come from
+ * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT
+ * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates,
+ * whose contents is similar to that environment variable but can be
+ * LF separated.  Its base points at a statically allocated buffer that
+ * contains "/the/directory/corresponding/to/.git/objects/...", while
+ * its name points just after the slash at the end of ".git/objects/"
+ * in the example above, and has enough space to hold 40-byte hex
+ * SHA1, an extra slash for the first level indirection, and the
+ * terminating NUL.
+ */
+static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth)
+{
+       struct stat st;
+       const char *objdir = get_object_directory();
+       struct alternate_object_database *ent;
+       struct alternate_object_database *alt;
+       /* 43 = 40-byte + 2 '/' + terminating NUL */
+       int pfxlen = len;
+       int entlen = pfxlen + 43;
+       int base_len = -1;
+
+       if (*entry != '/' && relative_base) {
+               /* Relative alt-odb */
+               if (base_len < 0)
+                       base_len = strlen(relative_base) + 1;
+               entlen += base_len;
+               pfxlen += base_len;
+       }
+       ent = xmalloc(sizeof(*ent) + entlen);
+
+       if (*entry != '/' && relative_base) {
+               memcpy(ent->base, relative_base, base_len - 1);
+               ent->base[base_len - 1] = '/';
+               memcpy(ent->base + base_len, entry, len);
+       }
+       else
+               memcpy(ent->base, entry, pfxlen);
+
+       ent->name = ent->base + pfxlen + 1;
+       ent->base[pfxlen + 3] = '/';
+       ent->base[pfxlen] = ent->base[entlen-1] = 0;
+
+       /* Detect cases where alternate disappeared */
+       if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) {
+               error("object directory %s does not exist; "
+                     "check .git/objects/info/alternates.",
+                     ent->base);
+               free(ent);
+               return -1;
+       }
+
+       /* Prevent the common mistake of listing the same
+        * thing twice, or object directory itself.
+        */
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               if (!memcmp(ent->base, alt->base, pfxlen)) {
+                       free(ent);
+                       return -1;
+               }
+       }
+       if (!memcmp(ent->base, objdir, pfxlen)) {
+               free(ent);
+               return -1;
+       }
+
+       /* add the alternate entry */
+       *alt_odb_tail = ent;
+       alt_odb_tail = &(ent->next);
+       ent->next = NULL;
+
+       /* recursively add alternates */
+       read_info_alternates(ent->base, depth + 1);
+
+       ent->base[pfxlen] = '/';
+
+       return 0;
+}
+
+static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
+                                const char *relative_base, int depth)
+{
+       const char *cp, *last;
+
+       if (depth > 5) {
+               error("%s: ignoring alternate object stores, nesting too deep.",
+                               relative_base);
+               return;
+       }
+
+       last = alt;
+       while (last < ep) {
+               cp = last;
+               if (cp < ep && *cp == '#') {
+                       while (cp < ep && *cp != sep)
+                               cp++;
+                       last = cp + 1;
+                       continue;
+               }
+               while (cp < ep && *cp != sep)
+                       cp++;
+               if (last != cp) {
+                       if ((*last != '/') && depth) {
+                               error("%s: ignoring relative alternate object store %s",
+                                               relative_base, last);
+                       } else {
+                               link_alt_odb_entry(last, cp - last,
+                                               relative_base, depth);
+                       }
+               }
+               while (cp < ep && *cp == sep)
+                       cp++;
+               last = cp;
+       }
+}
+
+static void read_info_alternates(const char * relative_base, int depth)
+{
+       char *map;
+       struct stat st;
+       char path[PATH_MAX];
+       int fd;
+
+       sprintf(path, "%s/info/alternates", relative_base);
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return;
+       if (fstat(fd, &st) || (st.st_size == 0)) {
+               close(fd);
+               return;
+       }
+       map = mmap(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);
+
+       munmap(map, st.st_size);
+}
+
+void prepare_alt_odb(void)
+{
+       char *alt;
+
+       alt = getenv(ALTERNATE_DB_ENVIRONMENT);
+       if (!alt) alt = "";
+
+       if (alt_odb_tail)
+               return;
+       alt_odb_tail = &alt_odb_list;
+       link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0);
+
+       read_info_alternates(get_object_directory(), 0);
+}
+
+static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
+{
+       char *name = sha1_file_name(sha1);
+       struct alternate_object_database *alt;
+
+       if (!stat(name, st))
+               return name;
+       prepare_alt_odb();
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               name = alt->name;
+               fill_sha1_path(name, sha1);
+               if (!stat(alt->base, st))
+                       return alt->base;
+       }
+       return NULL;
+}
+
+#define PACK_MAX_SZ (1<<26)
+static int pack_used_ctr;
+static unsigned long pack_mapped;
+struct packed_git *packed_git;
+
+static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
+                               void **idx_map_)
+{
+       void *idx_map;
+       unsigned int *index;
+       unsigned long idx_size;
+       int nr, i;
+       int fd = open(path, O_RDONLY);
+       struct stat st;
+       if (fd < 0)
+               return -1;
+       if (fstat(fd, &st)) {
+               close(fd);
+               return -1;
+       }
+       idx_size = st.st_size;
+       idx_map = mmap(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;
+       *idx_size_ = idx_size;
+
+       /* check index map */
+       if (idx_size < 4*256 + 20 + 20)
+               return error("index file too small");
+       nr = 0;
+       for (i = 0; i < 256; i++) {
+               unsigned int n = ntohl(index[i]);
+               if (n < nr)
+                       return error("non-monotonic index");
+               nr = n;
+       }
+
+       /*
+        * Total size:
+        *  - 256 index entries 4 bytes each
+        *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+        *  - 20-byte SHA1 of the packfile
+        *  - 20-byte SHA1 file checksum
+        */
+       if (idx_size != 4*256 + nr * 24 + 20 + 20)
+               return error("wrong index file size");
+
+       return 0;
+}
+
+static int unuse_one_packed_git(void)
+{
+       struct packed_git *p, *lru = NULL;
+
+       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;
+       }
+       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)
+{
+       p->pack_use_cnt--;
+}
+
+int use_packed_git(struct packed_git *p)
+{
+       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);
+               p->pack_size = st.st_size;
+       }
+       if (!p->pack_base) {
+               int fd;
+               struct stat st;
+               void *map;
+
+               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;
+
+               /* Check if the pack file matches with the index file.
+                * this is cheap.
+                */
+               if (memcmp((char*)(p->index_base) + p->index_size - 40,
+                          p->pack_base + p->pack_size - 20, 20)) {
+                             
+                       die("packfile %s does not match index.", p->pack_name);
+               }
+       }
+       p->pack_last_used = pack_used_ctr++;
+       p->pack_use_cnt++;
+       return 0;
+}
+
+struct packed_git *add_packed_git(char *path, int path_len, int local)
+{
+       struct stat st;
+       struct packed_git *p;
+       unsigned long idx_size;
+       void *idx_map;
+       unsigned char sha1[20];
+
+       if (check_packed_git_idx(path, &idx_size, &idx_map))
+               return NULL;
+
+       /* do we have a corresponding .pack file? */
+       strcpy(path + path_len - 4, ".pack");
+       if (stat(path, &st) || !S_ISREG(st.st_mode)) {
+               munmap(idx_map, idx_size);
+               return NULL;
+       }
+       /* ok, it looks sane as far as we can check without
+        * actually mapping the pack file.
+        */
+       p = xmalloc(sizeof(*p) + path_len + 2);
+       strcpy(p->pack_name, path);
+       p->index_size = idx_size;
+       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->pack_local = local;
+       if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1))
+               memcpy(p->sha1, sha1, 20);
+       return p;
+}
+
+struct packed_git *parse_pack_index(unsigned char *sha1)
+{
+       char *path = sha1_pack_index_name(sha1);
+       return parse_pack_index_file(sha1, path);
+}
+
+struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path)
+{
+       struct packed_git *p;
+       unsigned long idx_size;
+       void *idx_map;
+       char *path;
+
+       if (check_packed_git_idx(idx_path, &idx_size, &idx_map))
+               return NULL;
+
+       path = sha1_pack_name(sha1);
+
+       p = xmalloc(sizeof(*p) + strlen(path) + 2);
+       strcpy(p->pack_name, path);
+       p->index_size = idx_size;
+       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;
+       memcpy(p->sha1, sha1, 20);
+       return p;
+}
+
+void install_packed_git(struct packed_git *pack)
+{
+       pack->next = packed_git;
+       packed_git = pack;
+}
+
+static void prepare_packed_git_one(char *objdir, int local)
+{
+       char path[PATH_MAX];
+       int len;
+       DIR *dir;
+       struct dirent *de;
+
+       sprintf(path, "%s/pack", objdir);
+       len = strlen(path);
+       dir = opendir(path);
+       if (!dir) {
+               if (errno != ENOENT)
+                       error("unable to open object pack directory: %s: %s",
+                             path, strerror(errno));
+               return;
+       }
+       path[len++] = '/';
+       while ((de = readdir(dir)) != NULL) {
+               int namelen = strlen(de->d_name);
+               struct packed_git *p;
+
+               if (strcmp(de->d_name + namelen - 4, ".idx"))
+                       continue;
+
+               /* we have .idx.  Is it a file we can map? */
+               strcpy(path + len, de->d_name);
+               for (p = packed_git; p; p = p->next) {
+                       if (!memcmp(path, p->pack_name, len + namelen - 4))
+                               break;
+               }
+               if (p)
+                       continue;
+               p = add_packed_git(path, len + namelen, local);
+               if (!p)
+                       continue;
+               p->next = packed_git;
+               packed_git = p;
+       }
+       closedir(dir);
+}
+
+static int prepare_packed_git_run_once = 0;
+void prepare_packed_git(void)
+{
+       struct alternate_object_database *alt;
+
+       if (prepare_packed_git_run_once)
+               return;
+       prepare_packed_git_one(get_object_directory(), 1);
+       prepare_alt_odb();
+       for (alt = alt_odb_list; alt; alt = alt->next) {
+               alt->name[-1] = 0;
+               prepare_packed_git_one(alt->base, 0);
+               alt->name[-1] = '/';
+       }
+       prepare_packed_git_run_once = 1;
+}
+
+static void reprepare_packed_git(void)
+{
+       prepare_packed_git_run_once = 0;
+       prepare_packed_git();
+}
+
+int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+{
+       char header[100];
+       unsigned char real_sha1[20];
+       SHA_CTX c;
+
+       SHA1_Init(&c);
+       SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size));
+       SHA1_Update(&c, map, size);
+       SHA1_Final(real_sha1, &c);
+       return memcmp(sha1, real_sha1, 20) ? -1 : 0;
+}
+
+static void *map_sha1_file_internal(const unsigned char *sha1,
+                                   unsigned long *size)
+{
+       struct stat st;
+       void *map;
+       int fd;
+       char *filename = find_sha1_file(sha1, &st);
+
+       if (!filename) {
+               return NULL;
+       }
+
+       fd = open(filename, O_RDONLY | sha1_file_open_flag);
+       if (fd < 0) {
+               /* See if it works without O_NOATIME */
+               switch (sha1_file_open_flag) {
+               default:
+                       fd = open(filename, O_RDONLY);
+                       if (fd >= 0)
+                               break;
+               /* Fallthrough */
+               case 0:
+                       return NULL;
+               }
+
+               /* If it failed once, it will probably fail again.
+                * Stop using O_NOATIME
+                */
+               sha1_file_open_flag = 0;
+       }
+       map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (map == MAP_FAILED)
+               return NULL;
+       *size = st.st_size;
+       return map;
+}
+
+int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size)
+{
+       /* Get the data stream */
+       memset(stream, 0, sizeof(*stream));
+       stream->next_in = map;
+       stream->avail_in = mapsize;
+       stream->next_out = buffer;
+       stream->avail_out = size;
+
+       inflateInit(stream);
+       return inflate(stream, 0);
+}
+
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+{
+       int bytes = strlen(buffer) + 1;
+       unsigned char *buf = xmalloc(1+size);
+
+       memcpy(buf, buffer + bytes, stream->total_out - bytes);
+       bytes = stream->total_out - bytes;
+       if (bytes < size) {
+               stream->next_out = buf + bytes;
+               stream->avail_out = size - bytes;
+               while (inflate(stream, Z_FINISH) == Z_OK)
+                       /* nothing */;
+       }
+       buf[size] = 0;
+       inflateEnd(stream);
+       return buf;
+}
+
+/*
+ * We used to just use "sscanf()", but that's actually way
+ * too permissive for what we want to check. So do an anal
+ * object header parse by hand.
+ */
+int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+{
+       int i;
+       unsigned long size;
+
+       /*
+        * The type can be at most ten bytes (including the 
+        * terminating '\0' that we add), and is followed by
+        * a space. 
+        */
+       i = 10;
+       for (;;) {
+               char c = *hdr++;
+               if (c == ' ')
+                       break;
+               if (!--i)
+                       return -1;
+               *type++ = c;
+       }
+       *type = 0;
+
+       /*
+        * The length must follow immediately, and be in canonical
+        * decimal format (ie "010" is not valid).
+        */
+       size = *hdr++ - '0';
+       if (size > 9)
+               return -1;
+       if (size) {
+               for (;;) {
+                       unsigned long c = *hdr - '0';
+                       if (c > 9)
+                               break;
+                       hdr++;
+                       size = size * 10 + c;
+               }
+       }
+       *sizep = size;
+
+       /*
+        * The length must be followed by a zero byte
+        */
+       return *hdr ? -1 : 0;
+}
+
+void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size)
+{
+       int ret;
+       z_stream stream;
+       char hdr[8192];
+
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0)
+               return NULL;
+
+       return unpack_sha1_rest(&stream, hdr, *size);
+}
+
+/* forward declaration for a mutually recursive function */
+static int packed_object_info(struct pack_entry *entry,
+                             char *type, unsigned long *sizep);
+
+static int packed_delta_info(unsigned char *base_sha1,
+                            unsigned long delta_size,
+                            unsigned long left,
+                            char *type,
+                            unsigned long *sizep,
+                            struct packed_git *p)
+{
+       struct pack_entry base_ent;
+
+       if (left < 20)
+               die("truncated pack file");
+
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+
+       /* We choose to only get the type of the base object and
+        * ignore potentially corrupt pack file that expects the delta
+        * based on a base with a wrong size.  This saves tons of
+        * inflate() calls.
+        */
+
+       if (packed_object_info(&base_ent, type, NULL))
+               die("cannot get info for delta-pack base");
+
+       if (sizep) {
+               const unsigned char *data;
+               unsigned char delta_head[64];
+               unsigned long result_size;
+               z_stream stream;
+               int st;
+
+               memset(&stream, 0, sizeof(stream));
+
+               data = stream.next_in = base_sha1 + 20;
+               stream.avail_in = left - 20;
+               stream.next_out = delta_head;
+               stream.avail_out = sizeof(delta_head);
+
+               inflateInit(&stream);
+               st = inflate(&stream, Z_FINISH);
+               inflateEnd(&stream);
+               if ((st != Z_STREAM_END) &&
+                   stream.total_out != sizeof(delta_head))
+                       die("delta data unpack-initial failed");
+
+               /* Examine the initial part of the delta to figure out
+                * the result size.
+                */
+               data = delta_head;
+
+               /* ignore base size */
+               get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+
+               /* Read the result size */
+               result_size = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
+               *sizep = result_size;
+       }
+       return 0;
+}
+
+static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
+       enum object_type *type, unsigned long *sizep)
+{
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+
+       if (offset >= p->pack_size)
+               die("object offset outside of pack file");
+
+       pack =  p->pack_base + offset;
+       c = *pack++;
+       offset++;
+       *type = (c >> 4) & 7;
+       size = c & 15;
+       shift = 4;
+       while (c & 0x80) {
+               if (offset >= p->pack_size)
+                       die("object offset outside of pack file");
+               c = *pack++;
+               offset++;
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       *sizep = size;
+       return offset;
+}
+
+int check_reuse_pack_delta(struct packed_git *p, unsigned long offset,
+                          unsigned char *base, unsigned long *sizep,
+                          enum object_type *kindp)
+{
+       unsigned long ptr;
+       int status = -1;
+
+       use_packed_git(p);
+       ptr = offset;
+       ptr = unpack_object_header(p, ptr, kindp, sizep);
+       if (*kindp != OBJ_DELTA)
+               goto done;
+       memcpy(base, p->pack_base + ptr, 20);
+       status = 0;
+ done:
+       unuse_packed_git(p);
+       return status;
+}
+
+void packed_object_info_detail(struct pack_entry *e,
+                              char *type,
+                              unsigned long *size,
+                              unsigned long *store_size,
+                              unsigned int *delta_chain_length,
+                              unsigned char *base_sha1)
+{
+       struct packed_git *p = e->p;
+       unsigned long offset;
+       unsigned char *pack;
+       enum object_type kind;
+
+       offset = unpack_object_header(p, e->offset, &kind, size);
+       pack = p->pack_base + offset;
+       if (kind != OBJ_DELTA)
+               *delta_chain_length = 0;
+       else {
+               unsigned int chain_length = 0;
+               if (p->pack_size <= offset + 20)
+                       die("pack file %s records an incomplete delta base",
+                           p->pack_name);
+               memcpy(base_sha1, pack, 20);
+               do {
+                       struct pack_entry base_ent;
+                       unsigned long junk;
+
+                       find_pack_entry_one(pack, &base_ent, p);
+                       offset = unpack_object_header(p, base_ent.offset,
+                                                     &kind, &junk);
+                       pack = p->pack_base + offset;
+                       chain_length++;
+               } while (kind == OBJ_DELTA);
+               *delta_chain_length = chain_length;
+       }
+       switch (kind) {
+       case OBJ_COMMIT:
+               strcpy(type, commit_type);
+               break;
+       case OBJ_TREE:
+               strcpy(type, tree_type);
+               break;
+       case OBJ_BLOB:
+               strcpy(type, blob_type);
+               break;
+       case OBJ_TAG:
+               strcpy(type, tag_type);
+               break;
+       default:
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
+       }
+       *store_size = 0; /* notyet */
+}
+
+static int packed_object_info(struct pack_entry *entry,
+                             char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       unsigned long offset, size, left;
+       unsigned char *pack;
+       enum object_type kind;
+       int retval;
+
+       if (use_packed_git(p))
+               die("cannot map packed file");
+
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = packed_delta_info(pack, size, left, type, sizep, p);
+               unuse_packed_git(p);
+               return retval;
+       case OBJ_COMMIT:
+               strcpy(type, commit_type);
+               break;
+       case OBJ_TREE:
+               strcpy(type, tree_type);
+               break;
+       case OBJ_BLOB:
+               strcpy(type, blob_type);
+               break;
+       case OBJ_TAG:
+               strcpy(type, tag_type);
+               break;
+       default:
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
+       }
+       if (sizep)
+               *sizep = size;
+       unuse_packed_git(p);
+       return 0;
+}
+
+/* forward declaration for a mutually recursive function */
+static void *unpack_entry(struct pack_entry *, char *, unsigned long *);
+
+static void *unpack_delta_entry(unsigned char *base_sha1,
+                               unsigned long delta_size,
+                               unsigned long left,
+                               char *type,
+                               unsigned long *sizep,
+                               struct packed_git *p)
+{
+       struct pack_entry base_ent;
+       void *data, *delta_data, *result, *base;
+       unsigned long data_size, result_size, base_size;
+       z_stream stream;
+       int st;
+
+       if (left < 20)
+               die("truncated pack file");
+
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+       base = unpack_entry_gently(&base_ent, type, &base_size);
+       if (!base)
+               die("failed to read delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+
+       data = base_sha1 + 20;
+       data_size = left - 20;
+       delta_data = xmalloc(delta_size);
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_in = data;
+       stream.avail_in = data_size;
+       stream.next_out = delta_data;
+       stream.avail_out = delta_size;
+
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       inflateEnd(&stream);
+       if ((st != Z_STREAM_END) || stream.total_out != delta_size)
+               die("delta data unpack failed");
+
+       result = patch_delta(base, base_size,
+                            delta_data, delta_size,
+                            &result_size);
+       if (!result)
+               die("failed to apply delta");
+       free(delta_data);
+       free(base);
+       *sizep = result_size;
+       return result;
+}
+
+static void *unpack_non_delta_entry(unsigned char *data,
+                                   unsigned long size,
+                                   unsigned long left)
+{
+       int st;
+       z_stream stream;
+       unsigned char *buffer;
+
+       buffer = xmalloc(size + 1);
+       buffer[size] = 0;
+       memset(&stream, 0, sizeof(stream));
+       stream.next_in = data;
+       stream.avail_in = left;
+       stream.next_out = buffer;
+       stream.avail_out = size;
+
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       inflateEnd(&stream);
+       if ((st != Z_STREAM_END) || stream.total_out != size) {
+               free(buffer);
+               return NULL;
+       }
+
+       return buffer;
+}
+
+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(entry, 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 pack_entry *entry,
+                         char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       unsigned long offset, size, left;
+       unsigned char *pack;
+       enum object_type kind;
+       void *retval;
+
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = unpack_delta_entry(pack, size, left, type, sizep, p);
+               return retval;
+       case OBJ_COMMIT:
+               strcpy(type, commit_type);
+               break;
+       case OBJ_TREE:
+               strcpy(type, tree_type);
+               break;
+       case OBJ_BLOB:
+               strcpy(type, blob_type);
+               break;
+       case OBJ_TAG:
+               strcpy(type, tag_type);
+               break;
+       default:
+               return NULL;
+       }
+       *sizep = size;
+       retval = unpack_non_delta_entry(pack, size, left);
+       return retval;
+}
+
+int num_packed_objects(const struct packed_git *p)
+{
+       /* See check_packed_git_idx() */
+       return (p->index_size - 20 - 20 - 4*256) / 24;
+}
+
+int nth_packed_object_sha1(const struct packed_git *p, int n,
+                          unsigned char* sha1)
+{
+       void *index = p->index_base + 256;
+       if (n < 0 || num_packed_objects(p) <= n)
+               return -1;
+       memcpy(sha1, (index + 24 * n + 4), 20);
+       return 0;
+}
+
+int find_pack_entry_one(const unsigned char *sha1,
+                       struct pack_entry *e, struct packed_git *p)
+{
+       unsigned int *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;
+
+       do {
+               int mi = (lo + hi) / 2;
+               int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
+               if (!cmp) {
+                       e->offset = ntohl(*((unsigned int *)(index + 24 * mi)));
+                       memcpy(e->sha1, sha1, 20);
+                       e->p = p;
+                       return 1;
+               }
+               if (cmp > 0)
+                       hi = mi;
+               else
+                       lo = mi+1;
+       } while (lo < hi);
+       return 0;
+}
+
+static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+{
+       struct packed_git *p;
+       prepare_packed_git();
+
+       for (p = packed_git; p; p = p->next) {
+               if (find_pack_entry_one(sha1, e, p))
+                       return 1;
+       }
+       return 0;
+}
+
+struct packed_git *find_sha1_pack(const unsigned char *sha1, 
+                                 struct packed_git *packs)
+{
+       struct packed_git *p;
+       struct pack_entry e;
+
+       for (p = packs; p; p = p->next) {
+               if (find_pack_entry_one(sha1, &e, p))
+                       return p;
+       }
+       return NULL;
+       
+}
+
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+{
+       int status;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[128];
+
+       map = map_sha1_file_internal(sha1, &mapsize);
+       if (!map) {
+               struct pack_entry e;
+
+               if (find_pack_entry(sha1, &e))
+                       return packed_object_info(&e, type, sizep);
+               reprepare_packed_git();
+               if (find_pack_entry(sha1, &e))
+                       return packed_object_info(&e, type, sizep);
+               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));
+       if (parse_sha1_header(hdr, type, &size) < 0)
+               status = error("unable to parse %s header", sha1_to_hex(sha1));
+       else {
+               status = 0;
+               if (sizep)
+                       *sizep = size;
+       }
+       inflateEnd(&stream);
+       munmap(map, mapsize);
+       return status;
+}
+
+static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       struct pack_entry e;
+
+       if (!find_pack_entry(sha1, &e)) {
+               error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+               return NULL;
+       }
+       return unpack_entry(&e, type, size);
+}
+
+void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       unsigned long mapsize;
+       void *map, *buf;
+       struct pack_entry e;
+
+       if (find_pack_entry(sha1, &e))
+               return read_packed_sha1(sha1, type, size);
+       map = map_sha1_file_internal(sha1, &mapsize);
+       if (map) {
+               buf = unpack_sha1_file(map, mapsize, type, size);
+               munmap(map, mapsize);
+               return buf;
+       }
+       reprepare_packed_git();
+       if (find_pack_entry(sha1, &e))
+               return read_packed_sha1(sha1, type, size);
+       return NULL;
+}
+
+void *read_object_with_reference(const unsigned char *sha1,
+                                const char *required_type,
+                                unsigned long *size,
+                                unsigned char *actual_sha1_return)
+{
+       char type[20];
+       void *buffer;
+       unsigned long isize;
+       unsigned char actual_sha1[20];
+
+       memcpy(actual_sha1, sha1, 20);
+       while (1) {
+               int ref_length = -1;
+               const char *ref_type = NULL;
+
+               buffer = read_sha1_file(actual_sha1, type, &isize);
+               if (!buffer)
+                       return NULL;
+               if (!strcmp(type, required_type)) {
+                       *size = isize;
+                       if (actual_sha1_return)
+                               memcpy(actual_sha1_return, actual_sha1, 20);
+                       return buffer;
+               }
+               /* Handle references */
+               else if (!strcmp(type, commit_type))
+                       ref_type = "tree ";
+               else if (!strcmp(type, tag_type))
+                       ref_type = "object ";
+               else {
+                       free(buffer);
+                       return NULL;
+               }
+               ref_length = strlen(ref_type);
+
+               if (memcmp(buffer, ref_type, ref_length) ||
+                   get_sha1_hex(buffer + ref_length, actual_sha1)) {
+                       free(buffer);
+                       return NULL;
+               }
+               free(buffer);
+               /* Now we have the ID of the referred-to object in
+                * actual_sha1.  Check again. */
+       }
+}
+
+char *write_sha1_file_prepare(void *buf,
+                             unsigned long len,
+                             const char *type,
+                             unsigned char *sha1,
+                             unsigned char *hdr,
+                             int *hdrlen)
+{
+       SHA_CTX c;
+
+       /* Generate the header */
+       *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1;
+
+       /* Sha1.. */
+       SHA1_Init(&c);
+       SHA1_Update(&c, hdr, *hdrlen);
+       SHA1_Update(&c, buf, len);
+       SHA1_Final(sha1, &c);
+
+       return sha1_file_name(sha1);
+}
+
+/*
+ * Link the tempfile to the final place, possibly creating the
+ * last directory level as you do so.
+ *
+ * Returns the errno on failure, 0 on success.
+ */
+static int link_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret;
+
+       if (!link(tmpfile, filename))
+               return 0;
+
+       /*
+        * Try to mkdir the last path component if that failed
+        * with an ENOENT.
+        *
+        * Re-try the "link()" regardless of whether the mkdir
+        * succeeds, since a race might mean that somebody
+        * else succeeded.
+        */
+       ret = errno;
+       if (ret == ENOENT) {
+               char *dir = strrchr(filename, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(filename, 0777);
+                       if (adjust_shared_perm(filename))
+                               return -2;
+                       *dir = '/';
+                       if (!link(tmpfile, filename))
+                               return 0;
+                       ret = errno;
+               }
+       }
+       return ret;
+}
+
+/*
+ * Move the just written object into its final resting place
+ */
+int move_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret = link_temp_to_file(tmpfile, filename);
+
+       /*
+        * Coda hack - coda doesn't like cross-directory links,
+        * so we fall back to a rename, which will mean that it
+        * won't be able to check collisions, but that's not a
+        * big deal.
+        *
+        * The same holds for FAT formatted media.
+        *
+        * When this succeeds, we just return 0. We have nothing
+        * left to unlink.
+        */
+       if (ret && ret != EEXIST) {
+               if (!rename(tmpfile, filename))
+                       return 0;
+               ret = errno;
+       }
+       unlink(tmpfile);
+       if (ret) {
+               if (ret != EEXIST) {
+                       fprintf(stderr, "unable to write sha1 filename %s: %s\n", filename, strerror(ret));
+                       return -1;
+               }
+               /* FIXME!!! Collision check here ? */
+       }
+
+       return 0;
+}
+
+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 += size;
+       }
+       return 0;
+}
+
+int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+{
+       int size;
+       unsigned char *compressed;
+       z_stream stream;
+       unsigned char sha1[20];
+       char *filename;
+       static char tmpfile[PATH_MAX];
+       unsigned char hdr[50];
+       int fd, hdrlen;
+
+       /* Normally if we have it in the pack then we do not bother writing
+        * it out into .git/objects/??/?{38} file.
+        */
+       filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+       if (returnsha1)
+               memcpy(returnsha1, sha1, 20);
+       if (has_sha1_file(sha1))
+               return 0;
+       fd = open(filename, O_RDONLY);
+       if (fd >= 0) {
+               /*
+                * FIXME!!! We might do collision checking here, but we'd
+                * need to uncompress the old file and check it. Later.
+                */
+               close(fd);
+               return 0;
+       }
+
+       if (errno != ENOENT) {
+               fprintf(stderr, "sha1 file %s: %s\n", filename, strerror(errno));
+               return -1;
+       }
+
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+
+       fd = mkstemp(tmpfile);
+       if (fd < 0) {
+               fprintf(stderr, "unable to create temporary sha1 filename %s: %s\n", tmpfile, strerror(errno));
+               return -1;
+       }
+
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len+hdrlen);
+       compressed = xmalloc(size);
+
+       /* Compress it */
+       stream.next_out = compressed;
+       stream.avail_out = size;
+
+       /* First header.. */
+       stream.next_in = hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
+
+       /* Then the data itself.. */
+       stream.next_in = buf;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       size = stream.total_out;
+
+       if (write_buffer(fd, compressed, size) < 0)
+               die("unable to write sha1 file");
+       fchmod(fd, 0444);
+       close(fd);
+       free(compressed);
+
+       return move_temp_to_file(tmpfile, filename);
+}
+
+/*
+ * We need to unpack and recompress the object for writing
+ * it out to a different file.
+ */
+static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
+{
+       size_t size;
+       z_stream stream;
+       unsigned char *unpacked;
+       unsigned long len;
+       char type[20];
+       char hdr[50];
+       int hdrlen;
+       void *buf;
+
+       // need to unpack and recompress it by itself
+       unpacked = read_packed_sha1(sha1, type, &len);
+
+       hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len + hdrlen);
+       buf = xmalloc(size);
+
+       /* Compress it */
+       stream.next_out = buf;
+       stream.avail_out = size;
+
+       /* First header.. */
+       stream.next_in = (void *)hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
+
+       /* Then the data itself.. */
+       stream.next_in = unpacked;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       free(unpacked);
+
+       *objsize = stream.total_out;
+       return buf;
+}
+
+int write_sha1_to_fd(int fd, const unsigned char *sha1)
+{
+       int retval;
+       unsigned long objsize;
+       void *buf = map_sha1_file_internal(sha1, &objsize);
+
+       if (buf) {
+               retval = write_buffer(fd, buf, objsize);
+               munmap(buf, objsize);
+               return retval;
+       }
+
+       buf = repack_object(sha1, &objsize);
+       retval = write_buffer(fd, buf, objsize);
+       free(buf);
+       return retval;
+}
+
+int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
+                      size_t bufsize, size_t *bufposn)
+{
+       char tmpfile[PATH_MAX];
+       int local;
+       z_stream stream;
+       unsigned char real_sha1[20];
+       unsigned char discard[4096];
+       int ret;
+       SHA_CTX c;
+
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+
+       local = mkstemp(tmpfile);
+       if (local < 0)
+               return error("Couldn't open %s for %s",
+                            tmpfile, sha1_to_hex(sha1));
+
+       memset(&stream, 0, sizeof(stream));
+
+       inflateInit(&stream);
+
+       SHA1_Init(&c);
+
+       do {
+               ssize_t size;
+               if (*bufposn) {
+                       stream.avail_in = *bufposn;
+                       stream.next_in = (unsigned char *) buffer;
+                       do {
+                               stream.next_out = discard;
+                               stream.avail_out = sizeof(discard);
+                               ret = inflate(&stream, Z_SYNC_FLUSH);
+                               SHA1_Update(&c, discard, sizeof(discard) -
+                                           stream.avail_out);
+                       } while (stream.avail_in && ret == Z_OK);
+                       if (write_buffer(local, buffer, *bufposn - stream.avail_in) < 0)
+                               die("unable to write sha1 file");
+                       memmove(buffer, buffer + *bufposn - stream.avail_in,
+                               stream.avail_in);
+                       *bufposn = stream.avail_in;
+                       if (ret != Z_OK)
+                               break;
+               }
+               size = read(fd, buffer + *bufposn, bufsize - *bufposn);
+               if (size <= 0) {
+                       close(local);
+                       unlink(tmpfile);
+                       if (!size)
+                               return error("Connection closed?");
+                       perror("Reading from connection");
+                       return -1;
+               }
+               *bufposn += size;
+       } while (1);
+       inflateEnd(&stream);
+
+       close(local);
+       SHA1_Final(real_sha1, &c);
+       if (ret != Z_STREAM_END) {
+               unlink(tmpfile);
+               return error("File %s corrupted", sha1_to_hex(sha1));
+       }
+       if (memcmp(sha1, real_sha1, 20)) {
+               unlink(tmpfile);
+               return error("File %s has bad hash", sha1_to_hex(sha1));
+       }
+
+       return move_temp_to_file(tmpfile, sha1_file_name(sha1));
+}
+
+int has_pack_index(const unsigned char *sha1)
+{
+       struct stat st;
+       if (stat(sha1_pack_index_name(sha1), &st))
+               return 0;
+       return 1;
+}
+
+int has_pack_file(const unsigned char *sha1)
+{
+       struct stat st;
+       if (stat(sha1_pack_name(sha1), &st))
+               return 0;
+       return 1;
+}
+
+int has_sha1_pack(const unsigned char *sha1)
+{
+       struct pack_entry e;
+       return find_pack_entry(sha1, &e);
+}
+
+int has_sha1_file(const unsigned char *sha1)
+{
+       struct stat st;
+       struct pack_entry e;
+
+       if (find_pack_entry(sha1, &e))
+               return 1;
+       return find_sha1_file(sha1, &st) ? 1 : 0;
+}
+
+/*
+ * reads from fd as long as possible into a supplied buffer of size bytes.
+ * If neccessary the buffer's size is increased using realloc()
+ *
+ * returns 0 if anything went fine and -1 otherwise
+ *
+ * NOTE: both buf and size may change, but even when -1 is returned
+ * you still have to free() it yourself.
+ */
+int read_pipe(int fd, char** return_buf, unsigned long* return_size)
+{
+       char* buf = *return_buf;
+       unsigned long size = *return_size;
+       int iret;
+       unsigned long off = 0;
+
+       do {
+               iret = xread(fd, buf + off, size - off);
+               if (iret > 0) {
+                       off += iret;
+                       if (off == size) {
+                               size *= 2;
+                               buf = realloc(buf, size);
+                       }
+               }
+       } while (iret > 0);
+
+       *return_buf = buf;
+       *return_size = off;
+
+       if (iret < 0)
+               return -1;
+       return 0;
+}
+
+int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
+{
+       unsigned long size = 4096;
+       char *buf = malloc(size);
+       int ret;
+       unsigned char hdr[50];
+       int hdrlen;
+
+       if (read_pipe(fd, &buf, &size)) {
+               free(buf);
+               return -1;
+       }
+
+       if (!type)
+               type = blob_type;
+       if (write_object)
+               ret = write_sha1_file(buf, size, type, sha1);
+       else {
+               write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
+               ret = 0;
+       }
+       free(buf);
+       return ret;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type)
+{
+       unsigned long size = st->st_size;
+       void *buf;
+       int ret;
+       unsigned char hdr[50];
+       int hdrlen;
+
+       buf = "";
+       if (size)
+               buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+       close(fd);
+       if (buf == MAP_FAILED)
+               return -1;
+
+       if (!type)
+               type = blob_type;
+       if (write_object)
+               ret = write_sha1_file(buf, size, type, sha1);
+       else {
+               write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
+               ret = 0;
+       }
+       if (size)
+               munmap(buf, size);
+       return ret;
+}
+
+int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+{
+       int fd;
+       char *target;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("open(\"%s\"): %s", path,
+                                    strerror(errno));
+               if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+                       return error("%s: failed to insert into database",
+                                    path);
+               break;
+       case S_IFLNK:
+               target = xmalloc(st->st_size+1);
+               if (readlink(path, target, st->st_size+1) != st->st_size) {
+                       char *errstr = strerror(errno);
+                       free(target);
+                       return error("readlink(\"%s\"): %s", path,
+                                    errstr);
+               }
+               if (!write_object) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+                       write_sha1_file_prepare(target, st->st_size, blob_type,
+                                               sha1, hdr, &hdrlen);
+               } else if (write_sha1_file(target, st->st_size, blob_type, sha1))
+                       return error("%s: failed to insert into database",
+                                    path);
+               free(target);
+               break;
+       default:
+               return error("%s: unsupported file type", path);
+       }
+       return 0;
+}
diff --git a/sha1_name.c b/sha1_name.c
new file mode 100644 (file)
index 0000000..fbbde1c
--- /dev/null
@@ -0,0 +1,546 @@
+#include "cache.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tree-walk.h"
+#include "refs.h"
+
+static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
+{
+       struct alternate_object_database *alt;
+       char hex[40];
+       int found = 0;
+       static struct alternate_object_database *fakeent;
+
+       if (!fakeent) {
+               const char *objdir = get_object_directory();
+               int objdir_len = strlen(objdir);
+               int entlen = objdir_len + 43;
+               fakeent = xmalloc(sizeof(*fakeent) + entlen);
+               memcpy(fakeent->base, objdir, objdir_len);
+               fakeent->name = fakeent->base + objdir_len + 1;
+               fakeent->name[-1] = '/';
+       }
+       fakeent->next = alt_odb_list;
+
+       sprintf(hex, "%.2s", name);
+       for (alt = fakeent; alt && found < 2; alt = alt->next) {
+               struct dirent *de;
+               DIR *dir;
+               sprintf(alt->name, "%.2s/", name);
+               dir = opendir(alt->base);
+               if (!dir)
+                       continue;
+               while ((de = readdir(dir)) != NULL) {
+                       if (strlen(de->d_name) != 38)
+                               continue;
+                       if (memcmp(de->d_name, name + 2, len - 2))
+                               continue;
+                       if (!found) {
+                               memcpy(hex + 2, de->d_name, 38);
+                               found++;
+                       }
+                       else if (memcmp(hex + 2, de->d_name, 38)) {
+                               found = 2;
+                               break;
+                       }
+               }
+               closedir(dir);
+       }
+       if (found == 1)
+               return get_sha1_hex(hex, sha1) == 0;
+       return found;
+}
+
+static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
+{
+       do {
+               if (*a != *b)
+                       return 0;
+               a++;
+               b++;
+               len -= 2;
+       } while (len > 1);
+       if (len)
+               if ((*a ^ *b) & 0xf0)
+                       return 0;
+       return 1;
+}
+
+static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
+{
+       struct packed_git *p;
+       unsigned char found_sha1[20];
+       int found = 0;
+
+       prepare_packed_git();
+       for (p = packed_git; p && found < 2; p = p->next) {
+               unsigned num = num_packed_objects(p);
+               unsigned first = 0, last = num;
+               while (first < last) {
+                       unsigned mid = (first + last) / 2;
+                       unsigned char now[20];
+                       int cmp;
+
+                       nth_packed_object_sha1(p, mid, now);
+                       cmp = memcmp(match, now, 20);
+                       if (!cmp) {
+                               first = mid;
+                               break;
+                       }
+                       if (cmp > 0) {
+                               first = mid+1;
+                               continue;
+                       }
+                       last = mid;
+               }
+               if (first < num) {
+                       unsigned char now[20], next[20];
+                       nth_packed_object_sha1(p, first, now);
+                       if (match_sha(len, match, now)) {
+                               if (nth_packed_object_sha1(p, first+1, next) ||
+                                   !match_sha(len, match, next)) {
+                                       /* unique within this pack */
+                                       if (!found) {
+                                               memcpy(found_sha1, now, 20);
+                                               found++;
+                                       }
+                                       else if (memcmp(found_sha1, now, 20)) {
+                                               found = 2;
+                                               break;
+                                       }
+                               }
+                               else {
+                                       /* not even unique within this pack */
+                                       found = 2;
+                                       break;
+                               }
+                       }
+               }
+       }
+       if (found == 1)
+               memcpy(sha1, found_sha1, 20);
+       return found;
+}
+
+#define SHORT_NAME_NOT_FOUND (-1)
+#define SHORT_NAME_AMBIGUOUS (-2)
+
+static int find_unique_short_object(int len, char *canonical,
+                                   unsigned char *res, unsigned char *sha1)
+{
+       int has_unpacked, has_packed;
+       unsigned char unpacked_sha1[20], packed_sha1[20];
+
+       has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
+       has_packed = find_short_packed_object(len, res, packed_sha1);
+       if (!has_unpacked && !has_packed)
+               return SHORT_NAME_NOT_FOUND;
+       if (1 < has_unpacked || 1 < has_packed)
+               return SHORT_NAME_AMBIGUOUS;
+       if (has_unpacked != has_packed) {
+               memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20);
+               return 0;
+       }
+       /* Both have unique ones -- do they match? */
+       if (memcmp(packed_sha1, unpacked_sha1, 20))
+               return SHORT_NAME_AMBIGUOUS;
+       memcpy(sha1, packed_sha1, 20);
+       return 0;
+}
+
+static int get_short_sha1(const char *name, int len, unsigned char *sha1,
+                         int quietly)
+{
+       int i, status;
+       char canonical[40];
+       unsigned char res[20];
+
+       if (len < MINIMUM_ABBREV)
+               return -1;
+       memset(res, 0, 20);
+       memset(canonical, 'x', 40);
+       for (i = 0; i < len ;i++) {
+               unsigned char c = name[i];
+               unsigned char val;
+               if (c >= '0' && c <= '9')
+                       val = c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val = c - 'a' + 10;
+               else if (c >= 'A' && c <='F') {
+                       val = c - 'A' + 10;
+                       c -= 'A' - 'a';
+               }
+               else
+                       return -1;
+               canonical[i] = c;
+               if (!(i & 1))
+                       val <<= 4;
+               res[i >> 1] |= val;
+       }
+
+       status = find_unique_short_object(i, canonical, res, sha1);
+       if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
+               return error("short SHA1 %.*s is ambiguous.", len, canonical);
+       return status;
+}
+
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
+{
+       int status, is_null;
+       static char hex[41];
+
+       is_null = !memcmp(sha1, null_sha1, 20);
+       memcpy(hex, sha1_to_hex(sha1), 40);
+       if (len == 40)
+               return hex;
+       while (len < 40) {
+               unsigned char sha1_ret[20];
+               status = get_short_sha1(hex, len, sha1_ret, 1);
+               if (!status ||
+                   (is_null && status != SHORT_NAME_AMBIGUOUS)) {
+                       hex[len] = 0;
+                       return hex;
+               }
+               if (status != SHORT_NAME_AMBIGUOUS)
+                       return NULL;
+               len++;
+       }
+       return NULL;
+}
+
+static int ambiguous_path(const char *path, int len)
+{
+       int slash = 1;
+       int cnt;
+
+       for (cnt = 0; cnt < len; cnt++) {
+               switch (*path++) {
+               case '\0':
+                       break;
+               case '/':
+                       if (slash)
+                               break;
+                       slash = 1;
+                       continue;
+               case '.':
+                       continue;
+               default:
+                       slash = 0;
+                       continue;
+               }
+               break;
+       }
+       return slash;
+}
+
+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, *pathname;
+       char *real_path = NULL;
+       int refs_found = 0, am;
+       unsigned long at_time = (unsigned long)-1;
+       unsigned char *this_result;
+       unsigned char sha1_from_ref[20];
+
+       if (len == 40 && !get_sha1_hex(str, sha1))
+               return 0;
+
+       /* At a given period of time? "@{2 hours ago}" */
+       for (am = 1; am < len - 1; am++) {
+               if (str[am] == '@' && str[am+1] == '{' && str[len-1] == '}') {
+                       int date_len = len - am - 3;
+                       char *date_spec = xmalloc(date_len + 1);
+                       strncpy(date_spec, str + am + 2, date_len);
+                       date_spec[date_len] = 0;
+                       at_time = approxidate(date_spec);
+                       free(date_spec);
+                       len = am;
+                       break;
+               }
+       }
+
+       /* Accept only unambiguous ref paths. */
+       if (ambiguous_path(str, len))
+               return -1;
+
+       for (p = fmt; *p; p++) {
+               this_result = refs_found ? sha1_from_ref : sha1;
+               pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+               if (pathname) {
+                       if (!refs_found++)
+                               real_path = strdup(pathname);
+                       if (!warn_ambiguous_refs)
+                               break;
+               }
+       }
+
+       if (!refs_found)
+               return -1;
+
+       if (warn_ambiguous_refs && refs_found > 1)
+               fprintf(stderr, warning, len, str);
+
+       if (at_time != (unsigned long)-1) {
+               read_ref_at(
+                       real_path + strlen(git_path(".")) - 1,
+                       at_time,
+                       sha1);
+       }
+
+       free(real_path);
+       return 0;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
+static int get_parent(const char *name, int len,
+                     unsigned char *result, int idx)
+{
+       unsigned char sha1[20];
+       int ret = get_sha1_1(name, len, sha1);
+       struct commit *commit;
+       struct commit_list *p;
+
+       if (ret)
+               return ret;
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return -1;
+       if (parse_commit(commit))
+               return -1;
+       if (!idx) {
+               memcpy(result, commit->object.sha1, 20);
+               return 0;
+       }
+       p = commit->parents;
+       while (p) {
+               if (!--idx) {
+                       memcpy(result, p->item->object.sha1, 20);
+                       return 0;
+               }
+               p = p->next;
+       }
+       return -1;
+}
+
+static int get_nth_ancestor(const char *name, int len,
+                           unsigned char *result, int generation)
+{
+       unsigned char sha1[20];
+       int ret = get_sha1_1(name, len, sha1);
+       if (ret)
+               return ret;
+
+       while (generation--) {
+               struct commit *commit = lookup_commit_reference(sha1);
+
+               if (!commit || parse_commit(commit) || !commit->parents)
+                       return -1;
+               memcpy(sha1, commit->parents->item->object.sha1, 20);
+       }
+       memcpy(result, sha1, 20);
+       return 0;
+}
+
+static int peel_onion(const char *name, int len, unsigned char *sha1)
+{
+       unsigned char outer[20];
+       const char *sp;
+       const char *type_string = NULL;
+       struct object *o;
+
+       /*
+        * "ref^{type}" dereferences ref repeatedly until you cannot
+        * dereference anymore, or you get an object of given type,
+        * whichever comes first.  "ref^{}" means just dereference
+        * tags until you get a non-tag.  "ref^0" is a shorthand for
+        * "ref^{commit}".  "commit^{tree}" could be used to find the
+        * top-level tree of the given commit.
+        */
+       if (len < 4 || name[len-1] != '}')
+               return -1;
+
+       for (sp = name + len - 1; name <= sp; sp--) {
+               int ch = *sp;
+               if (ch == '{' && name < sp && sp[-1] == '^')
+                       break;
+       }
+       if (sp <= name)
+               return -1;
+
+       sp++; /* beginning of type name, or closing brace for empty */
+       if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
+               type_string = commit_type;
+       else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
+               type_string = tree_type;
+       else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
+               type_string = blob_type;
+       else if (sp[0] == '}')
+               type_string = NULL;
+       else
+               return -1;
+
+       if (get_sha1_1(name, sp - name - 2, outer))
+               return -1;
+
+       o = parse_object(outer);
+       if (!o)
+               return -1;
+       if (!type_string) {
+               o = deref_tag(o, name, sp - name - 2);
+               if (!o || (!o->parsed && !parse_object(o->sha1)))
+                       return -1;
+               memcpy(sha1, o->sha1, 20);
+       }
+       else {
+               /* At this point, the syntax look correct, so
+                * if we do not get the needed object, we should
+                * barf.
+                */
+
+               while (1) {
+                       if (!o || (!o->parsed && !parse_object(o->sha1)))
+                               return -1;
+                       if (o->type == type_string) {
+                               memcpy(sha1, o->sha1, 20);
+                               return 0;
+                       }
+                       if (o->type == tag_type)
+                               o = ((struct tag*) o)->tagged;
+                       else if (o->type == commit_type)
+                               o = &(((struct commit *) o)->tree->object);
+                       else
+                               return error("%.*s: expected %s type, but the object dereferences to %s type",
+                                            len, name, type_string,
+                                            o->type);
+                       if (!o->parsed)
+                               parse_object(o->sha1);
+               }
+       }
+       return 0;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1)
+{
+       int ret, has_suffix;
+       const char *cp;
+
+       /* "name~3" is "name^^^",
+        * "name~" and "name~0" are name -- not "name^0"!
+        * "name^" is not "name^0"; it is "name^1".
+        */
+       has_suffix = 0;
+       for (cp = name + len - 1; name <= cp; cp--) {
+               int ch = *cp;
+               if ('0' <= ch && ch <= '9')
+                       continue;
+               if (ch == '~' || ch == '^')
+                       has_suffix = ch;
+               break;
+       }
+
+       if (has_suffix) {
+               int num = 0;
+               int len1 = cp - name;
+               cp++;
+               while (cp < name + len)
+                       num = num * 10 + *cp++ - '0';
+               if (has_suffix == '^') {
+                       if (!num && len1 == len - 1)
+                               num = 1;
+                       return get_parent(name, len1, sha1, num);
+               }
+               /* else if (has_suffix == '~') -- goes without saying */
+               return get_nth_ancestor(name, len1, sha1, num);
+       }
+
+       ret = peel_onion(name, len, sha1);
+       if (!ret)
+               return 0;
+
+       ret = get_sha1_basic(name, len, sha1);
+       if (!ret)
+               return 0;
+       return get_short_sha1(name, len, sha1, 0);
+}
+
+/*
+ * This is like "get_sha1_basic()", except it allows "sha1 expressions",
+ * notably "xyz^" for "parent of xyz"
+ */
+int get_sha1(const char *name, unsigned char *sha1)
+{
+       int ret, bracket_depth;
+       unsigned unused;
+       int namelen = strlen(name);
+       const char *cp;
+
+       prepare_alt_odb();
+       ret = get_sha1_1(name, namelen, sha1);
+       if (!ret)
+               return ret;
+       /* sha1:path --> object name of path in ent sha1
+        * :path -> object name of path in index
+        * :[0-3]:path -> object name of path in index at stage
+        */
+       if (name[0] == ':') {
+               int stage = 0;
+               struct cache_entry *ce;
+               int pos;
+               if (namelen < 3 ||
+                   name[2] != ':' ||
+                   name[1] < '0' || '3' < name[1])
+                       cp = name + 1;
+               else {
+                       stage = name[1] - '0';
+                       cp = name + 3;
+               }
+               namelen = namelen - (cp - name);
+               if (!active_cache)
+                       read_cache();
+               if (active_nr < 0)
+                       return -1;
+               pos = cache_name_pos(cp, namelen);
+               if (pos < 0)
+                       pos = -pos - 1;
+               while (pos < active_nr) {
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) != namelen ||
+                           memcmp(ce->name, cp, namelen))
+                               break;
+                       if (ce_stage(ce) == stage) {
+                               memcpy(sha1, ce->sha1, 20);
+                               return 0;
+                       }
+                       pos++;
+               }
+               return -1;
+       }
+       for (cp = name, bracket_depth = 0; *cp; cp++) {
+               if (*cp == '{')
+                       bracket_depth++;
+               else if (bracket_depth && *cp == '}')
+                       bracket_depth--;
+               else if (!bracket_depth && *cp == ':')
+                       break;
+       }
+       if (*cp == ':') {
+               unsigned char tree_sha1[20];
+               if (!get_sha1_1(name, cp-name, tree_sha1))
+                       return get_tree_entry(tree_sha1, cp+1, sha1,
+                                             &unused);
+       }
+       return ret;
+}
diff --git a/shell.c b/shell.c
new file mode 100644 (file)
index 0000000..8c08cf0
--- /dev/null
+++ b/shell.c
@@ -0,0 +1,61 @@
+#include "cache.h"
+#include "quote.h"
+#include "exec_cmd.h"
+
+static int do_generic_cmd(const char *me, char *arg)
+{
+       const char *my_argv[4];
+
+       if (!arg || !(arg = sq_dequote(arg)))
+               die("bad argument");
+       if (strncmp(me, "git-", 4))
+               die("bad command");
+
+       my_argv[0] = me + 4;
+       my_argv[1] = arg;
+       my_argv[2] = NULL;
+
+       return execv_git_cmd(my_argv);
+}
+
+static struct commands {
+       const char *name;
+       int (*exec)(const char *me, char *arg);
+} cmd_list[] = {
+       { "git-receive-pack", do_generic_cmd },
+       { "git-upload-pack", do_generic_cmd },
+       { NULL },
+};
+
+int main(int argc, char **argv)
+{
+       char *prog;
+       struct commands *cmd;
+
+       /* We want to see "-c cmd args", and nothing else */
+       if (argc != 3 || strcmp(argv[1], "-c"))
+               die("What do you think I am? A shell?");
+
+       prog = argv[2];
+       argv += 2;
+       argc -= 2;
+       for (cmd = cmd_list ; cmd->name ; cmd++) {
+               int len = strlen(cmd->name);
+               char *arg;
+               if (strncmp(cmd->name, prog, len))
+                       continue;
+               arg = NULL;
+               switch (prog[len]) {
+               case '\0':
+                       arg = NULL;
+                       break;
+               case ' ':
+                       arg = prog + len + 1;
+                       break;
+               default:
+                       continue;
+               }
+               exit(cmd->exec(cmd->name, arg));
+       }
+       die("unrecognized command '%s'", prog);
+}
diff --git a/show-index.c b/show-index.c
new file mode 100644 (file)
index 0000000..c21d660
--- /dev/null
@@ -0,0 +1,28 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       int i;
+       unsigned nr;
+       unsigned int entry[6];
+       static unsigned int top_index[256];
+
+       if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
+               die("unable to read idex");
+       nr = 0;
+       for (i = 0; i < 256; i++) {
+               unsigned n = ntohl(top_index[i]);
+               if (n < nr)
+                       die("corrupt index file");
+               nr = n;
+       }
+       for (i = 0; i < nr; i++) {
+               unsigned offset;
+
+               if (fread(entry, 24, 1, stdin) != 1)
+                       die("unable to read entry %u/%u", i, nr);
+               offset = ntohl(entry[0]);
+               printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+       }
+       return 0;
+}
diff --git a/ssh-fetch.c b/ssh-fetch.c
new file mode 100644 (file)
index 0000000..e3067b8
--- /dev/null
@@ -0,0 +1,174 @@
+#ifndef COUNTERPART_ENV_NAME
+#define COUNTERPART_ENV_NAME "GIT_SSH_UPLOAD"
+#endif
+#ifndef COUNTERPART_PROGRAM_NAME
+#define COUNTERPART_PROGRAM_NAME "git-ssh-upload"
+#endif
+#ifndef MY_PROGRAM_NAME
+#define MY_PROGRAM_NAME "git-ssh-fetch"
+#endif
+
+#include "cache.h"
+#include "commit.h"
+#include "rsh.h"
+#include "fetch.h"
+#include "refs.h"
+
+static int fd_in;
+static int fd_out;
+
+static unsigned char remote_version = 0;
+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, buffer + ret, length - ret);
+               if (size < 0) {
+                       return size;
+               }
+               if (size == 0) {
+                       return ret;
+               }
+               ret += size;
+       }
+       return ret;
+}
+
+static int prefetches = 0;
+
+static struct object_list *in_transit = NULL;
+static struct object_list **end_of_transit = &in_transit;
+
+void prefetch(unsigned char *sha1)
+{
+       char type = 'o';
+       struct object_list *node;
+       if (prefetches > 100) {
+               fetch(in_transit->item->sha1);
+       }
+       node = xmalloc(sizeof(struct object_list));
+       node->next = NULL;
+       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);
+       prefetches++;
+}
+
+static char conn_buf[4096];
+static size_t conn_buf_posn = 0;
+
+int fetch(unsigned char *sha1)
+{
+       int ret;
+       signed char remote;
+       struct object_list *temp;
+
+       if (memcmp(sha1, in_transit->item->sha1, 20)) {
+               // we must have already fetched it to clean the queue
+               return has_sha1_file(sha1) ? 0 : -1;
+       }
+       prefetches--;
+       temp = in_transit;
+       in_transit = in_transit->next;
+       if (!in_transit)
+               end_of_transit = &in_transit;
+       free(temp);
+
+       if (conn_buf_posn) {
+               remote = conn_buf[0];
+               memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
+       } else {
+               if (read(fd_in, &remote, 1) < 1)
+                       return -1;
+       }
+       //fprintf(stderr, "Got %d\n", remote);
+       if (remote < 0)
+               return remote;
+       ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
+       if (!ret)
+               pull_say("got %s\n", sha1_to_hex(sha1));
+       return ret;
+}
+
+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) {
+               return error("Couldn't read version from remote end");
+       }
+       return 0;
+}
+
+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);
+       if (remote < 0)
+               return remote;
+       read(fd_in, sha1, 20);
+       return 0;
+}
+
+static const char ssh_fetch_usage[] =
+  MY_PROGRAM_NAME
+  " [-c] [-t] [-a] [-v] [-d] [--recover] [-w ref] commit-id url";
+int main(int argc, char **argv)
+{
+       char *commit_id;
+       char *url;
+       int arg = 1;
+       const char *prog;
+
+       prog = getenv("GIT_SSH_PUSH");
+       if (!prog) prog = "git-ssh-upload";
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 't') {
+                       get_tree = 1;
+               } else if (argv[arg][1] == 'c') {
+                       get_history = 1;
+               } else if (argv[arg][1] == 'a') {
+                       get_all = 1;
+                       get_tree = 1;
+                       get_history = 1;
+               } else if (argv[arg][1] == 'v') {
+                       get_verbosely = 1;
+               } else if (argv[arg][1] == 'w') {
+                       write_ref = argv[arg + 1];
+                       arg++;
+               } else if (!strcmp(argv[arg], "--recover")) {
+                       get_recover = 1;
+               }
+               arg++;
+       }
+       if (argc < arg + 2) {
+               usage(ssh_fetch_usage);
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+       write_ref_log_details = url;
+
+       if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+               return 1;
+
+       if (get_version())
+               return 1;
+
+       if (pull(commit_id))
+               return 1;
+
+       return 0;
+}
diff --git a/ssh-pull.c b/ssh-pull.c
new file mode 100644 (file)
index 0000000..868ce4d
--- /dev/null
@@ -0,0 +1,4 @@
+#define COUNTERPART_ENV_NAME "GIT_SSH_PUSH"
+#define COUNTERPART_PROGRAM_NAME "git-ssh-push"
+#define MY_PROGRAM_NAME "git-ssh-pull"
+#include "ssh-fetch.c"
diff --git a/ssh-push.c b/ssh-push.c
new file mode 100644 (file)
index 0000000..a562df1
--- /dev/null
@@ -0,0 +1,4 @@
+#define COUNTERPART_ENV_NAME "GIT_SSH_PULL"
+#define COUNTERPART_PROGRAM_NAME "git-ssh-pull"
+#define MY_PROGRAM_NAME "git-ssh-push"
+#include "ssh-upload.c"
diff --git a/ssh-upload.c b/ssh-upload.c
new file mode 100644 (file)
index 0000000..2da6661
--- /dev/null
@@ -0,0 +1,146 @@
+#ifndef COUNTERPART_ENV_NAME
+#define COUNTERPART_ENV_NAME "GIT_SSH_FETCH"
+#endif
+#ifndef COUNTERPART_PROGRAM_NAME
+#define COUNTERPART_PROGRAM_NAME "git-ssh-fetch"
+#endif
+#ifndef MY_PROGRAM_NAME
+#define MY_PROGRAM_NAME "git-ssh-upload"
+#endif
+
+#include "cache.h"
+#include "rsh.h"
+#include "refs.h"
+
+#include <string.h>
+
+static unsigned char local_version = 1;
+static unsigned char remote_version = 0;
+
+static int verbose = 0;
+
+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);
+       
+       if (verbose)
+               fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
+
+       remote = 0;
+       
+       if (!has_sha1_file(sha1)) {
+               fprintf(stderr, "git-ssh-upload: could not find %s\n",
+                       sha1_to_hex(sha1));
+               remote = -1;
+       }
+       
+       write(fd_out, &remote, 1);
+       
+       if (remote < 0)
+               return 0;
+       
+       return write_sha1_to_fd(fd_out, sha1);
+}
+
+static int serve_version(int fd_in, int fd_out)
+{
+       if (read(fd_in, &remote_version, 1) < 1)
+               return -1;
+       write(fd_out, &local_version, 1);
+       return 0;
+}
+
+static int serve_ref(int fd_in, int fd_out)
+{
+       char ref[PATH_MAX];
+       unsigned char sha1[20];
+       int posn = 0;
+       signed char remote = 0;
+       do {
+               if (read(fd_in, ref + posn, 1) < 1)
+                       return -1;
+               posn++;
+       } while (ref[posn - 1]);
+
+       if (verbose)
+               fprintf(stderr, "Serving %s\n", ref);
+
+       if (get_ref_sha1(ref, sha1))
+               remote = -1;
+       write(fd_out, &remote, 1);
+       if (remote)
+               return 0;
+       write(fd_out, sha1, 20);
+        return 0;
+}
+
+
+static void service(int fd_in, int fd_out) {
+       char type;
+       int retval;
+       do {
+               retval = read(fd_in, &type, 1);
+               if (retval < 1) {
+                       if (retval < 0)
+                               perror("git-ssh-upload: read ");
+                       return;
+               }
+               if (type == 'v' && serve_version(fd_in, fd_out))
+                       return;
+               if (type == 'o' && serve_object(fd_in, fd_out))
+                       return;
+               if (type == 'r' && serve_ref(fd_in, fd_out))
+                       return;
+       } while (1);
+}
+
+static const char ssh_push_usage[] =
+       MY_PROGRAM_NAME " [-c] [-t] [-a] [-w ref] commit-id url";
+
+int main(int argc, char **argv)
+{
+       int arg = 1;
+        char *commit_id;
+        char *url;
+       int fd_in, fd_out;
+       const char *prog;
+       unsigned char sha1[20];
+       char hex[41];
+
+       prog = getenv(COUNTERPART_ENV_NAME);
+       if (!prog) prog = COUNTERPART_PROGRAM_NAME;
+
+       setup_git_directory();
+
+       while (arg < argc && argv[arg][0] == '-') {
+               if (argv[arg][1] == 'w')
+                       arg++;
+                arg++;
+        }
+       if (argc < arg + 2)
+               usage(ssh_push_usage);
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+       if (get_sha1(commit_id, sha1))
+               die("Not a valid object name %s", commit_id);
+       memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
+       argv[arg] = hex;
+
+       if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+               return 1;
+
+       service(fd_in, fd_out);
+       return 0;
+}
diff --git a/strbuf.c b/strbuf.c
new file mode 100644 (file)
index 0000000..9d9d8be
--- /dev/null
+++ b/strbuf.c
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "strbuf.h"
+#include "cache.h"
+
+void strbuf_init(struct strbuf *sb) {
+       sb->buf = NULL;
+       sb->eof = sb->alloc = sb->len = 0;
+}
+
+static void strbuf_begin(struct strbuf *sb) {
+       free(sb->buf);
+       strbuf_init(sb);
+}
+
+static void inline strbuf_add(struct strbuf *sb, int ch) {
+       if (sb->alloc <= sb->len) {
+               sb->alloc = sb->alloc * 3 / 2 + 16;
+               sb->buf = xrealloc(sb->buf, sb->alloc);
+       }
+       sb->buf[sb->len++] = ch;
+}
+
+static void strbuf_end(struct strbuf *sb) {
+       strbuf_add(sb, 0);
+}
+
+void read_line(struct strbuf *sb, FILE *fp, int term) {
+       int ch;
+       strbuf_begin(sb);
+       if (feof(fp)) {
+               sb->eof = 1;
+               return;
+       }
+       while ((ch = fgetc(fp)) != EOF) {
+               if (ch == term)
+                       break;
+               strbuf_add(sb, ch);
+       }
+       if (ch == EOF && sb->len == 0)
+               sb->eof = 1;
+       strbuf_end(sb);
+}
+
diff --git a/strbuf.h b/strbuf.h
new file mode 100644 (file)
index 0000000..74cc012
--- /dev/null
+++ b/strbuf.h
@@ -0,0 +1,13 @@
+#ifndef STRBUF_H
+#define STRBUF_H
+struct strbuf {
+       int alloc;
+       int len;
+       int eof;
+       char *buf;
+};
+
+extern void strbuf_init(struct strbuf *);
+extern void read_line(struct strbuf *, FILE *, int);
+
+#endif /* STRBUF_H */
diff --git a/stripspace.c b/stripspace.c
new file mode 100644 (file)
index 0000000..65a6346
--- /dev/null
@@ -0,0 +1,55 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * Remove empty lines from the beginning and end.
+ *
+ * Turn multiple consecutive empty lines into just one
+ * empty line.  Return true if it is an incomplete line.
+ */
+static int cleanup(char *line)
+{
+       int len = strlen(line);
+
+       if (len && line[len-1] == '\n') {
+               if (len == 1)
+                       return 0;
+               do {
+                       unsigned char c = line[len-2];
+                       if (!isspace(c))
+                               break;
+                       line[len-2] = '\n';
+                       len--;
+                       line[len] = 0;
+               } while (len > 1);
+               return 0;
+       }
+       return 1;
+}
+
+int main(int argc, char **argv)
+{
+       int empties = -1;
+       int incomplete = 0;
+       char line[1024];
+
+       while (fgets(line, sizeof(line), stdin)) {
+               incomplete = cleanup(line);
+
+               /* Not just an empty line? */
+               if (line[0] != '\n') {
+                       if (empties > 0)
+                               putchar('\n');
+                       empties = 0;
+                       fputs(line, stdout);
+                       continue;
+               }
+               if (empties < 0)
+                       continue;
+               empties++;
+       }
+       if (incomplete)
+               putchar('\n');
+       return 0;
+}
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644 (file)
index 0000000..193c87c
--- /dev/null
@@ -0,0 +1,35 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static void check_symref(const char *HEAD)
+{
+       unsigned char sha1[20];
+       const char *git_HEAD = strdup(git_path("%s", HEAD));
+       const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+       if (git_refs_heads_master) {
+               /* we want to strip the .git/ part */
+               int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+               puts(git_refs_heads_master + pfxlen);
+       }
+       else
+               die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+       setup_git_directory();
+       git_config(git_default_config);
+       switch (argc) {
+       case 2:
+               check_symref(argv[1]);
+               break;
+       case 3:
+               create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+               break;
+       default:
+               usage(git_symbolic_ref_usage);
+       }
+       return 0;
+}
diff --git a/t/.gitignore b/t/.gitignore
new file mode 100644 (file)
index 0000000..fad67c0
--- /dev/null
@@ -0,0 +1 @@
+trash
diff --git a/t/Makefile b/t/Makefile
new file mode 100644 (file)
index 0000000..5495985
--- /dev/null
@@ -0,0 +1,29 @@
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+#GIT_TEST_OPTS=--verbose --debug
+SHELL_PATH ?= $(SHELL)
+TAR ?= $(TAR)
+
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+ifdef NO_PYTHON
+       GIT_TEST_OPTS += --no-python
+endif
+
+all: $(T) clean
+
+$(T):
+       @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
+
+clean:
+       rm -fr trash
+
+.PHONY: $(T) clean
+.NOTPARALLEL:
+
diff --git a/t/README b/t/README
new file mode 100644 (file)
index 0000000..ac5a3ac
--- /dev/null
+++ b/t/README
@@ -0,0 +1,208 @@
+Core GIT Tests
+==============
+
+This directory holds many test scripts for core GIT tools.  The
+first part of this short document describes how to run the tests
+and read their output.
+
+When fixing the tools or adding enhancements, you are strongly
+encouraged to add tests in this directory to cover what you are
+trying to fix or enhance.  The later part of this short document
+describes how your test scripts should be organized.
+
+
+Running Tests
+-------------
+
+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 2: .git/objects should have 256 subdirectories.
+    *   ok 3: git-update-index without --add should fail adding.
+    ...
+    *   ok 23: no diff after checkout and git-update-index --refresh.
+    * passed all 23 test(s)
+    *** t0100-environment-names.sh ***
+    *   ok 1: using old names should issue warnings.
+    *   ok 2: using old names but having new names should not issue warnings.
+    ...
+
+Or you can run each test individually from command line, like
+this:
+
+    $ sh ./t3001-ls-files-killed.sh
+    *   ok 1: git-update-index --add to add various paths.
+    *   ok 2: git-ls-files -k to show killed files.
+    *   ok 3: validate git-ls-files -k output.
+    * passed all 3 test(s)
+
+You can pass --verbose (or -v), --debug (or -d), and --immediate
+(or -i) command line argument to the test.
+
+--verbose::
+       This makes the test more verbose.  Specifically, the
+       command being run and their output if any are also
+       output.
+
+--debug::
+       This may help the person who is developing a new test.
+       It causes the command defined with test_debug to run.
+
+--immediate::
+       This causes the test to immediately exit upon the first
+       failed test.
+
+
+Naming Tests
+------------
+
+The test files are named as:
+
+       tNNNN-commandname-details.sh
+
+where N is a decimal digit.
+
+First digit tells the family:
+
+       0 - the absolute basics and global stuff
+       1 - the basic commands concerning database
+       2 - the basic commands concerning the working tree
+       3 - the other basic commands (e.g. ls-files)
+       4 - the diff commands
+       5 - the pull and exporting commands
+       6 - the revision tree commands (even e.g. merge-base)
+
+Second digit tells the particular command we are testing.
+
+Third digit (optionally) tells the particular switch or group of switches
+we are testing.
+
+If you create files under t/ directory (i.e. here) that is not
+the top-level test script, never name the file to match the above
+pattern.  The Makefile here considers all such files as the
+top-level test script and tries to run all of them.  A care is
+especially needed if you are creating a common test library
+file, similar to test-lib.sh, because such a library file may
+not be suitable for standalone execution.
+
+
+Writing Tests
+-------------
+
+The test script is written as a shell script.  It should start
+with the standard "#!/bin/sh" with copyright notices, and an
+assignment to variable 'test_description', like this:
+
+       #!/bin/sh
+       #
+       # Copyright (c) 2005 Junio C Hamano
+       #
+
+       test_description='xxx test (option --frotz)
+
+       This test registers the following structure in the cache
+       and tries to run git-ls-files with option --frotz.'
+
+
+Source 'test-lib.sh'
+--------------------
+
+After assigning test_description, the test script should source
+test-lib.sh like this:
+
+       . ./test-lib.sh
+
+This test harness library does the following things:
+
+ - If the script is invoked with command line argument --help
+   (or -h), it shows the test_description and exits.
+
+ - Creates an empty test directory with an empty .git/objects
+   database and chdir(2) into it.  This directory is 't/trash'
+   if you must know, but I do not think you care.
+
+ - Defines standard test helper functions for your scripts to
+   use.  These functions are designed to make all scripts behave
+   consistently when command line arguments --verbose (or -v),
+   --debug (or -d), and --immediate (or -i) is given.
+
+
+End with test_done
+------------------
+
+Your script will be a sequence of tests, using helper functions
+from the test harness library.  At the end of the script, call
+'test_done'.
+
+
+Test harness library
+--------------------
+
+There are a handful helper functions defined in the test harness
+library for your script to use.
+
+ - test_expect_success <message> <script>
+
+   This takes two strings as parameter, and evaluates the
+   <script>.  If it yields success, test is considered
+   successful.  <message> should state what it is testing.
+
+   Example:
+
+       test_expect_success \
+           'git-write-tree should be able to write an empty tree.' \
+           'tree=$(git-write-tree)'
+
+ - test_expect_failure <message> <script>
+
+   This is the opposite of test_expect_success.  If <script>
+   yields success, test is considered a failure.
+
+   Example:
+
+       test_expect_failure \
+           'git-update-index without --add should fail adding.' \
+           'git-update-index should-be-empty'
+
+ - test_debug <script>
+
+   This takes a single argument, <script>, and evaluates it only
+   when the test script is started with --debug command line
+   argument.  This is primarily meant for use during the
+   development of a new test script.
+
+ - test_done
+
+   Your test script must have test_done at the end.  Its purpose
+   is to summarize successes and failures in the test script and
+   exit with an appropriate error code.
+
+
+Tips for Writing Tests
+----------------------
+
+As with any programming projects, existing programs are the best
+source of the information.  However, do _not_ emulate
+t0000-basic.sh when writing your tests.  The test is special in
+that it tries to validate the very core of GIT.  For example, it
+knows that there will be 256 subdirectories under .git/objects/,
+and it knows that the object ID of an empty tree is a certain
+40-byte string.  This is deliberately done so in t0000-basic.sh
+because the things the very basic core test tries to achieve is
+to serve as a basis for people who are changing the GIT internal
+drastically.  For these people, after making certain changes,
+not seeing failures from the basic test _is_ a failure.  And
+such drastic changes to the core GIT that even changes these
+otherwise supposedly stable object IDs should be accompanied by
+an update to t0000-basic.sh.
+
+However, other tests that simply rely on basic parts of the core
+GIT working properly should not have that level of intimate
+knowledge of the core GIT internals.  If all the test scripts
+hardcoded the object IDs like t0000-basic.sh does, that defeats
+the purpose of t0000-basic.sh, which is to isolate that level of
+validation in one place.  Your test also ends up needing
+updating when such a change to the internal happens, so do _not_
+do it and leave the low level of validation to t0000-basic.sh.
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
new file mode 100644 (file)
index 0000000..114938c
--- /dev/null
@@ -0,0 +1,121 @@
+# This file isn't used as a test script directly, instead it is
+# sourced from t8001-annotate.sh and t8001-blame.sh.
+
+check_count () {
+       head=
+       case "$1" in -h) head="$2"; shift; shift ;; esac
+       $PROG file $head | perl -e '
+               my %expect = (@ARGV);
+               my %count = ();
+               while (<STDIN>) {
+                       if (/^[0-9a-f]+\t\(([^\t]+)\t/) {
+                               my $author = $1;
+                               for ($author) { s/^\s*//; s/\s*$//; }
+                               if (exists $expect{$author}) {
+                                       $count{$author}++;
+                               }
+                       }
+               }
+               my $bad = 0;
+               while (my ($author, $count) = each %count) {
+                       my $ok;
+                       if ($expect{$author} != $count) {
+                               $bad = 1;
+                               $ok = "bad";
+                       }
+                       else {
+                               $ok = "good";
+                       }
+                       print STDERR "Author $author (expected $expect{$author}, attributed $count) $ok\n";
+               }
+               exit($bad);
+       ' "$@"
+}
+
+test_expect_success \
+    'prepare reference tree' \
+    'echo "1A quick brown fox jumps over the" >file &&
+     echo "lazy dog" >>file &&
+     git add file
+     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+    'check all lines blamed on A' \
+    'check_count A 2'
+
+test_expect_success \
+    'Setup new lines blamed on B' \
+    'echo "2A quick brown fox jumps over the" >>file &&
+     echo "lazy dog" >> file &&
+     GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+    'Two lines blamed on A, two on B' \
+    'check_count A 2 B 2'
+
+test_expect_success \
+    'merge-setup part 1' \
+    'git checkout -b branch1 master &&
+     echo "3A slow green fox jumps into the" >> file &&
+     echo "well." >> file &&
+     GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+    'Two lines blamed on A, two on B, two on B1' \
+    'check_count A 2 B 2 B1 2'
+
+test_expect_success \
+    'merge-setup part 2' \
+    'git checkout -b branch2 master &&
+     sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
+     mv file.new file &&
+     GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, one on B2' \
+    'check_count A 2 B 1 B2 1'
+
+test_expect_success \
+    'merge-setup part 3' \
+    'git pull . branch1'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, two on B1, one on B2' \
+    'check_count A 2 B 1 B1 2 B2 1'
+
+test_expect_success \
+    'Annotating an old revision works' \
+    'check_count -h master A 2 B 2'
+
+test_expect_success \
+    'Annotating an old revision works' \
+    'check_count -h master^ A 2'
+
+test_expect_success \
+    'merge-setup part 4' \
+    'echo "evil merge." >>file &&
+     EDITOR=: VISUAL=: git commit -a --amend'
+
+test_expect_success \
+    'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \
+    'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1'
+
+test_expect_success \
+    'an incomplete line added' \
+    'echo "incomplete" | tr -d "\\012" >>file &&
+    GIT_AUTHOR_NAME="C" git commit -a -m "Incomplete"'
+
+test_expect_success \
+    'With incomplete lines.' \
+    'check_count A 2 B 1 B1 2 B2 1 "A U Thor" 1 C 1'
+
+test_expect_success \
+    'some edit' \
+    'mv file file1 &&
+     sed -e 1d -e "5s/3A/99/" file1 >file &&
+     rm -f file1 &&
+    GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
+
+test_expect_success \
+    'some edit' \
+    'check_count A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1'
diff --git a/t/diff-lib.sh b/t/diff-lib.sh
new file mode 100755 (executable)
index 0000000..745a1b0
--- /dev/null
@@ -0,0 +1,41 @@
+:
+
+_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"
+sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*  / X X \1#       /'
+compare_diff_raw () {
+    # When heuristics are improved, the score numbers would change.
+    # Ignore them while comparing.
+    # Also we do not check SHA1 hash generation in this test, which
+    # is a job for t0000-basic.sh
+
+    sed -e "$sanitize_diff_raw" <"$1" >.tmp-1
+    sed -e "$sanitize_diff_raw" <"$2" >.tmp-2
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/'
+compare_diff_raw_z () {
+    # When heuristics are improved, the score numbers would change.
+    # Ignore them while comparing.
+    # Also we do not check SHA1 hash generation in this test, which
+    # is a job for t0000-basic.sh
+
+    tr '\0' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
+    tr '\0' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+compare_diff_patch () {
+    # When heuristics are improved, the score numbers would change.
+    # Ignore them while comparing.
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$1" >.tmp-1
+    sed -e '
+       /^[dis]*imilarity index [0-9]*%$/d
+       /^index [0-9a-f]*\.\.[0-9a-f]/d
+    ' <"$2" >.tmp-2
+    diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh
new file mode 100755 (executable)
index 0000000..d195603
--- /dev/null
@@ -0,0 +1,158 @@
+: Included from t1000-read-tree-m-3way.sh and others
+# Original tree.
+mkdir Z
+for a in N D M
+do
+    for b in N D M
+    do
+        p=$a$b
+       echo This is $p from the original tree. >$p
+       echo This is Z/$p from the original tree. >Z/$p
+       test_expect_success \
+           "adding test file $p and Z/$p" \
+           'git-update-index --add $p &&
+           git-update-index --add Z/$p'
+    done
+done
+echo This is SS from the original tree. >SS
+test_expect_success \
+    'adding test file SS' \
+    'git-update-index --add SS'
+cat >TT <<\EOF
+This is a trivial merge sample text.
+Branch A is expected to upcase this word, here.
+There are some filler lines to avoid diff context
+conflicts here,
+like this one,
+and this one,
+and this one is yet another one of them.
+At the very end, here comes another line, that is
+the word, expected to be upcased by Branch B.
+This concludes the trivial merge sample file.
+EOF
+test_expect_success \
+    'adding test file TT' \
+    'git-update-index --add TT'
+test_expect_success \
+    'prepare initial tree' \
+    'tree_O=$(git-write-tree)'
+
+################################################################
+# Branch A and B makes the changes according to the above matrix.
+
+################################################################
+# Branch A
+
+to_remove=$(echo D? Z/D?)
+rm -f $to_remove
+test_expect_success \
+    'change in branch A (removal)' \
+    'git-update-index --remove $to_remove'
+
+for p in M? Z/M?
+do
+    echo This is modified $p in the branch A. >$p
+    test_expect_success \
+       'change in branch A (modification)' \
+        "git-update-index $p"
+done
+
+for p in AN AA Z/AN Z/AA
+do
+    echo This is added $p in the branch A. >$p
+    test_expect_success \
+       'change in branch A (addition)' \
+       "git-update-index --add $p"
+done
+
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+    'change in branch A (addition)' \
+    'git-update-index --add LL &&
+     git-update-index SS'
+mv TT TT-
+sed -e '/Branch A/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+    'change in branch A (edit)' \
+    'git-update-index TT'
+
+mkdir DF
+echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
+test_expect_success \
+    'change in branch A (change file to directory)' \
+    'git-update-index --add DF/DF'
+
+test_expect_success \
+    'recording branch A tree' \
+    'tree_A=$(git-write-tree)'
+          
+################################################################
+# Branch B
+# Start from O
+
+rm -rf [NDMASLT][NDMASLT] Z DF
+mkdir Z
+test_expect_success \
+    'reading original tree and checking out' \
+    'git-read-tree $tree_O &&
+     git-checkout-index -a'
+
+to_remove=$(echo ?D Z/?D)
+rm -f $to_remove
+test_expect_success \
+    'change in branch B (removal)' \
+    "git-update-index --remove $to_remove"
+
+for p in ?M Z/?M
+do
+    echo This is modified $p in the branch B. >$p
+    test_expect_success \
+       'change in branch B (modification)' \
+       "git-update-index $p"
+done
+
+for p in NA AA Z/NA Z/AA
+do
+    echo This is added $p in the branch B. >$p
+    test_expect_success \
+       'change in branch B (addition)' \
+       "git-update-index --add $p"
+done
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+    'change in branch B (addition and modification)' \
+    'git-update-index --add LL &&
+     git-update-index SS'
+mv TT TT-
+sed -e '/Branch B/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+    'change in branch B (modification)' \
+    'git-update-index TT'
+
+echo Branch B makes a file at DF. >DF
+test_expect_success \
+    'change in branch B (addition of a file to conflict with directory)' \
+    'git-update-index --add DF'
+
+test_expect_success \
+    'recording branch B tree' \
+    'tree_B=$(git-write-tree)'
+
+test_expect_success \
+    'keep contents of 3 trees for easy access' \
+    'rm -f .git/index &&
+     git-read-tree $tree_O &&
+     mkdir .orig-O &&
+     git-checkout-index --prefix=.orig-O/ -f -q -a &&
+     rm -f .git/index &&
+     git-read-tree $tree_A &&
+     mkdir .orig-A &&
+     git-checkout-index --prefix=.orig-A/ -f -q -a &&
+     rm -f .git/index &&
+     git-read-tree $tree_B &&
+     mkdir .orig-B &&
+     git-checkout-index --prefix=.orig-B/ -f -q -a'
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
new file mode 100755 (executable)
index 0000000..cf33989
--- /dev/null
@@ -0,0 +1,257 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test the very basics part #1.
+
+The rest of the test suite does not check the basic operation of git
+plumbing commands to work very carefully.  Their job is to concentrate
+on tricky features that caused bugs in the past to detect regression.
+
+This test runs very basic features, like registering things in cache,
+writing tree, etc.
+
+Note that this test *deliberately* hard-codes many expected object
+IDs.  When object ID computation changes, like in the previous case of
+swapping compression and hashing order, the person who is making the
+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.
+
+../git >/dev/null
+if test $? != 1
+then
+       echo >&2 'You do not seem to have built git yet.'
+       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.
+# 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.' \
+    'cmp -s /dev/null should-be-empty' 
+
+# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
+# 3 is counting "objects" itself
+find .git/objects -type d -print >full-of-directories
+test_expect_success \
+    '.git/objects should have 3 subdirectories.' \
+    'test $(wc -l < full-of-directories) = 3'
+
+################################################################
+# Basics of the basics
+
+# updating a new file without --add should fail.
+test_expect_failure \
+    'git-update-index without --add should fail adding.' \
+    'git-update-index should-be-empty'
+
+# and with --add it should succeed, even if it is empty (it used to fail).
+test_expect_success \
+    'git-update-index with --add should succeed.' \
+    'git-update-index --add should-be-empty'
+
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree=$(git-write-tree)'
+
+# we know the shape and contents of the tree and know the object ID for it.
+test_expect_success \
+    'validate object ID of a known tree.' \
+    'test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a'
+
+# Removing paths.
+rm -f should-be-empty full-of-directories
+test_expect_failure \
+    'git-update-index without --remove should fail removing.' \
+    'git-update-index should-be-empty'
+
+test_expect_success \
+    'git-update-index with --remove should be able to remove.' \
+    'git-update-index --remove should-be-empty'
+
+# Empty tree can be written with recent write-tree.
+test_expect_success \
+    'git-write-tree should be able to write an empty tree.' \
+    'tree=$(git-write-tree)'
+
+test_expect_success \
+    'validate object ID of a known tree.' \
+    'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+
+# Various types of objects
+mkdir path2 path3 path3/subp3
+for p in path0 path2/file2 path3/file3 path3/subp3/file3
+do
+    echo "hello $p" >$p
+    ln -s "hello $p" ${p}sym
+done
+test_expect_success \
+    'adding various types of objects with git-update-index --add.' \
+    'find path* ! -type d -print | xargs git-update-index --add'
+
+# Show them and see that matches what we expect.
+test_expect_success \
+    'showing stage with git-ls-files --stage' \
+    'git-ls-files --stage >current'
+
+cat >expected <<\EOF
+100644 f87290f8eb2cbbea7857214459a0739927eab154 0      path0
+120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0      path0sym
+100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0      path2/file2
+120000 d8ce161addc5173867a3c3c730924388daedbc38 0      path2/file2sym
+100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0      path3/file3
+120000 8599103969b43aff7e430efea79ca4636466794f 0      path3/file3sym
+100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0      path3/subp3/file3
+120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0      path3/subp3/file3sym
+EOF
+test_expect_success \
+    'validate git-ls-files output for a known tree.' \
+    'diff current expected'
+
+test_expect_success \
+    'writing tree out with git-write-tree.' \
+    'tree=$(git-write-tree)'
+test_expect_success \
+    'validate object ID for a known tree.' \
+    'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b'
+
+test_expect_success \
+    'showing tree with git-ls-tree' \
+    'git-ls-tree $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+EOF
+test_expect_success \
+    'git-ls-tree output for a known tree.' \
+    'diff current expected'
+
+# This changed in ls-tree pathspec change -- recursive does
+# not show tree nodes anymore.
+test_expect_success \
+    'showing tree with git-ls-tree -r' \
+    'git-ls-tree -r $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38   path2/file2sym
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376   path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f   path3/file3sym
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
+# But with -r -t we can have both.
+test_expect_success \
+    'showing tree with git-ls-tree -r -t' \
+    'git-ls-tree -r -t $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38   path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376   path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f   path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2   path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
+################################################################
+rm .git/index
+test_expect_success \
+    'git-read-tree followed by write-tree should be idempotent.' \
+    'git-read-tree $tree &&
+     test -f .git/index &&
+     newtree=$(git-write-tree) &&
+     test "$newtree" = "$tree"'
+
+cat >expected <<\EOF
+:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M     path0
+:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M     path0sym
+:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M     path2/file2
+:120000 120000 d8ce161addc5173867a3c3c730924388daedbc38 0000000000000000000000000000000000000000 M     path2/file2sym
+:100644 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0000000000000000000000000000000000000000 M     path3/file3
+:120000 120000 8599103969b43aff7e430efea79ca4636466794f 0000000000000000000000000000000000000000 M     path3/file3sym
+:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M     path3/subp3/file3
+:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M     path3/subp3/file3sym
+EOF
+test_expect_success \
+    'validate git-diff-files output for a know cache/work tree state.' \
+    'git-diff-files >current && diff >/dev/null -b current expected'
+
+test_expect_success \
+    'git-update-index --refresh should succeed.' \
+    'git-update-index --refresh'
+
+test_expect_success \
+    'no diff after checkout and git-update-index --refresh.' \
+    'git-diff-files >current && cmp -s current /dev/null'
+
+################################################################
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
+test_expect_success \
+    'git-commit-tree records the correct tree in a commit.' \
+    'commit0=$(echo NO | git-commit-tree $P) &&
+     tree=$(git show --pretty=raw $commit0 |
+        sed -n -e "s/^tree //p" -e "/^author /q") &&
+     test "z$tree" = "z$P"'
+
+test_expect_success \
+    'git-commit-tree records the correct parent in a commit.' \
+    'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+     parent=$(git show --pretty=raw $commit1 |
+        sed -n -e "s/^parent //p" -e "/^author /q") &&
+     test "z$commit0" = "z$parent"'
+
+test_expect_success \
+    'git-commit-tree omits duplicated parent in a commit.' \
+    'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+     parent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        sort -u) &&
+     test "z$commit0" = "z$parent" &&
+     numparent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        wc -l) &&
+     test $numparent = 1'
+
+test_done
diff --git a/t/t0010-racy-git.sh b/t/t0010-racy-git.sh
new file mode 100755 (executable)
index 0000000..e45a9e4
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='racy GIT'
+
+. ./test-lib.sh
+
+# This test can give false success if your machine is sufficiently
+# slow or your trial happened to happen on second boundary.
+
+for trial in 0 1 2 3 4
+do
+       rm -f .git/index
+       echo frotz >infocom
+       git update-index --add infocom
+       echo xyzzy >infocom
+
+       files=`git diff-files -p`
+       test_expect_success \
+       "Racy GIT trial #$trial part A" \
+       'test "" != "$files"'
+
+       sleep 1
+       echo xyzzy >cornerstone
+       git update-index --add cornerstone
+
+       files=`git diff-files -p`
+       test_expect_success \
+       "Racy GIT trial #$trial part B" \
+       'test "" != "$files"'
+
+done
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
new file mode 100755 (executable)
index 0000000..d0af8c3
--- /dev/null
@@ -0,0 +1,514 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Three way merge with read-tree -m
+
+This test tries three-way merge with read-tree -m
+
+There is one ancestor (called O for Original) and two branches A
+and B derived from it.  We want to do a 3-way merge between A and
+B, using O as the common ancestor.
+
+    merge A O B
+
+Decisions are made by comparing contents of O, A and B pathname
+by pathname.  The result is determined by the following guiding
+principle:
+
+ - If only A does something to it and B does not touch it, take
+   whatever A does.
+
+ - If only B does something to it and A does not touch it, take
+   whatever B does.
+
+ - If both A and B does something but in the same way, take
+   whatever they do.
+
+ - If A and B does something but different things, we need a
+   3-way merge:
+
+   - We cannot do anything about the following cases:
+
+     * O does not have it.  A and B both must be adding to the
+       same path independently.
+
+     * A deletes it.  B must be modifying.
+
+   - Otherwise, A and B are modifying.  Run 3-way merge.
+
+First, the case matrix.
+
+ - Vertical axis is for A'\''s actions.
+ - Horizontal axis is for B'\''s actions.
+
+.----------------------------------------------------------------.
+| A        B | No Action  |   Delete   |   Modify   |    Add     |
+|------------+------------+------------+------------+------------|
+| No Action  |            |            |            |            |
+|            | select O   | delete     | select B   | select B   |
+|            |            |            |            |            |
+|------------+------------+------------+------------+------------|
+| Delete     |            |            | ********** |    can     |
+|            | delete     | delete     | merge      |    not     |
+|            |            |            |            |  happen    |
+|------------+------------+------------+------------+------------|
+| Modify     |            | ********** | ?????????? |    can     |
+|            | select A   | merge      | select A=B |    not     |
+|            |            |            | merge      |  happen    |
+|------------+------------+------------+------------+------------|
+| Add        |            |    can     |    can     | ?????????? |
+|            | select A   |    not     |    not     | select A=B |
+|            |            |  happen    |  happen    | merge      |
+.----------------------------------------------------------------.
+
+In addition:
+
+ SS: a special case of MM, where A and B makes the same modification.
+ LL: a special case of AA, where A and B creates the same file.
+ TT: a special case of MM, where A and B makes mergeable changes.
+ DF: a special case, where A makes a directory and B makes a file.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+################################################################
+# Trivial "majority when 3 stages exist" merge plus #2ALT, #3ALT
+# and #5ALT trivial merges.
+
+cat >expected <<\EOF
+100644 X 2     AA
+100644 X 3     AA
+100644 X 0     AN
+100644 X 1     DD
+100644 X 3     DF
+100644 X 2     DF/DF
+100644 X 1     DM
+100644 X 3     DM
+100644 X 1     DN
+100644 X 3     DN
+100644 X 0     LL
+100644 X 1     MD
+100644 X 2     MD
+100644 X 1     MM
+100644 X 2     MM
+100644 X 3     MM
+100644 X 0     MN
+100644 X 0     NA
+100644 X 1     ND
+100644 X 2     ND
+100644 X 0     NM
+100644 X 0     NN
+100644 X 0     SS
+100644 X 1     TT
+100644 X 2     TT
+100644 X 3     TT
+100644 X 2     Z/AA
+100644 X 3     Z/AA
+100644 X 0     Z/AN
+100644 X 1     Z/DD
+100644 X 1     Z/DM
+100644 X 3     Z/DM
+100644 X 1     Z/DN
+100644 X 3     Z/DN
+100644 X 1     Z/MD
+100644 X 2     Z/MD
+100644 X 1     Z/MM
+100644 X 2     Z/MM
+100644 X 3     Z/MM
+100644 X 0     Z/MN
+100644 X 0     Z/NA
+100644 X 1     Z/ND
+100644 X 2     Z/ND
+100644 X 0     Z/NM
+100644 X 0     Z/NN
+EOF
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+check_result () {
+    git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+    diff -u expected current
+}
+
+# This is done on an empty work directory, which is the normal
+# merge person behaviour.
+test_expect_success \
+    '3-way merge with git-read-tree -m, empty cache' \
+    "rm -fr [NDMALTS][NDMALTSF] Z &&
+     rm .git/index &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+# This starts out with the first head, which is the normal
+# patch submitter behaviour.
+test_expect_success \
+    '3-way merge with git-read-tree -m, match H' \
+    "rm -fr [NDMALTS][NDMALTSF] Z &&
+     rm .git/index &&
+     git-read-tree $tree_A &&
+     git-checkout-index -f -u -a &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+: <<\END_OF_CASE_TABLE
+
+We have so far tested only empty index and clean-and-matching-A index
+case which are trivial.  Make sure index requirements are also
+checked.
+
+"git-read-tree -m O A B"
+
+     O       A       B         result      index requirements
+-------------------------------------------------------------------
+  1  missing missing missing   -           must not exist.
+ ------------------------------------------------------------------
+  2  missing missing exists    take B*     must match B, if exists.
+ ------------------------------------------------------------------
+  3  missing exists  missing   take A*     must match A, if exists.
+ ------------------------------------------------------------------
+  4  missing exists  A!=B      no merge    must match A and be
+                                           up-to-date, if exists.
+ ------------------------------------------------------------------
+  5  missing exists  A==B      take A      must match A, if exists.
+ ------------------------------------------------------------------
+  6  exists  missing missing   remove      must not exist.
+ ------------------------------------------------------------------
+  7  exists  missing O!=B      no merge    must not exist.
+ ------------------------------------------------------------------
+  8  exists  missing O==B      remove      must not exist.
+ ------------------------------------------------------------------
+  9  exists  O!=A    missing   no merge    must match A and be
+                                           up-to-date, if exists.
+ ------------------------------------------------------------------
+ 10  exists  O==A    missing   remove      ditto
+ ------------------------------------------------------------------
+ 11  exists  O!=A    O!=B      no merge    must match A and be
+                     A!=B                  up-to-date, if exists.
+ ------------------------------------------------------------------
+ 12  exists  O!=A    O!=B      take A      must match A, if exists.
+                     A==B
+ ------------------------------------------------------------------
+ 13  exists  O!=A    O==B      take A      must match A, if exists.
+ ------------------------------------------------------------------
+ 14  exists  O==A    O!=B      take B      if exists, must either (1)
+                                           match A and be up-to-date,
+                                           or (2) match B.
+ ------------------------------------------------------------------
+ 15  exists  O==A    O==B      take B      must match A if exists.
+ ------------------------------------------------------------------
+ 16  exists  O==A    O==B      barf        must match A if exists.
+     *multi* in one  in another
+-------------------------------------------------------------------
+
+Note: we need to be careful in case 2 and 3.  The tree A may contain
+DF (file) when tree B require DF to be a directory by having DF/DF
+(file).
+
+END_OF_CASE_TABLE
+
+test_expect_failure \
+    '1 - must not have an entry not in A.' \
+    "rm -f .git/index XX &&
+     echo XX >XX &&
+     git-update-index --add XX &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '2 - must match B in !O && !A && B case.' \
+    "rm -f .git/index NA &&
+     cp .orig-B/NA NA &&
+     git-update-index --add NA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '2 - matching B alone is OK in !O && !A && B case.' \
+    "rm -f .git/index NA &&
+     cp .orig-B/NA NA &&
+     git-update-index --add NA &&
+     echo extra >>NA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '3 - must match A in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     git-update-index --add AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '3 - matching A alone is OK in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     git-update-index --add AN &&
+     echo extra >>AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '3 (fail) - must match A in !O && A && !B case.' \
+    "rm -f .git/index AN &&
+     cp .orig-A/AN AN &&
+     echo extra >>AN &&
+     git-update-index --add AN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     git-update-index --add AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     git-update-index --add AA &&
+     echo extra >>AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+    "rm -f .git/index AA &&
+     cp .orig-A/AA AA &&
+     echo extra >>AA &&
+     git-update-index --add AA &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '5 - must match in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     git-update-index --add LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '5 - must match in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     git-update-index --add LL &&
+     echo extra >>LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '5 (fail) - must match A in !O && A && B && A==B case.' \
+    "rm -f .git/index LL &&
+     cp .orig-A/LL LL &&
+     echo extra >>LL &&
+     git-update-index --add LL &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '6 - must not exist in O && !A && !B case' \
+    "rm -f .git/index DD &&
+     echo DD >DD
+     git-update-index --add DD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '7 - must not exist in O && !A && B && O!=B case' \
+    "rm -f .git/index DM &&
+     cp .orig-B/DM DM &&
+     git-update-index --add DM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '8 - must not exist in O && !A && B && O==B case' \
+    "rm -f .git/index DN &&
+     cp .orig-B/DN DN &&
+     git-update-index --add DN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '9 - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     git-update-index --add MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     git-update-index --add MD &&
+     echo extra >>MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+    "rm -f .git/index MD &&
+     cp .orig-A/MD MD &&
+     echo extra >>MD &&
+     git-update-index --add MD &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '10 - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     git-update-index --add ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     git-update-index --add ND &&
+     echo extra >>ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+    "rm -f .git/index ND &&
+     cp .orig-A/ND ND &&
+     echo extra >>ND &&
+     git-update-index --add ND &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     git-update-index --add MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     git-update-index --add MM &&
+     echo extra >>MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+    "rm -f .git/index MM &&
+     cp .orig-A/MM MM &&
+     echo extra >>MM &&
+     git-update-index --add MM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '12 - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     git-update-index --add SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '12 - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     git-update-index --add SS &&
+     echo extra >>SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '12 (fail) - must match A in O && A && B && O!=A && A==B case' \
+    "rm -f .git/index SS &&
+     cp .orig-A/SS SS &&
+     echo extra >>SS &&
+     git-update-index --add SS &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '13 - must match A in O && A && B && O!=A && O==B case' \
+    "rm -f .git/index MN &&
+     cp .orig-A/MN MN &&
+     git-update-index --add MN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '13 - must match A in O && A && B && O!=A && O==B case' \
+    "rm -f .git/index MN &&
+     cp .orig-A/MN MN &&
+     git-update-index --add MN &&
+     echo extra >>MN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     git-update-index --add NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '14 - may match B in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-B/NM NM &&
+     git-update-index --add NM &&
+     echo extra >>NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     git-update-index --add NM &&
+     echo extra >>NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+    "rm -f .git/index NM &&
+     cp .orig-A/NM NM &&
+     echo extra >>NM &&
+     git-update-index --add NM &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+    '15 - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     git-update-index --add NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_success \
+    '15 - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     git-update-index --add NN &&
+     echo extra >>NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B &&
+     check_result"
+
+test_expect_failure \
+    '15 (fail) - must match A in O && A && B && O==A && O==B case' \
+    "rm -f .git/index NN &&
+     cp .orig-A/NN NN &&
+     echo extra >>NN &&
+     git-update-index --add NN &&
+     git-read-tree -m $tree_O $tree_A $tree_B"
+
+# #16
+test_expect_success \
+    '16 - A matches in one and B matches in another.' \
+    'rm -f .git/index F16 &&
+    echo F16 >F16 &&
+    git-update-index --add F16 &&
+    tree0=`git-write-tree` &&
+    echo E16 >F16 &&
+    git-update-index F16 &&
+    tree1=`git-write-tree` &&
+    git-read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+    git-ls-files --stage'
+
+test_done
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
new file mode 100755 (executable)
index 0000000..75e4c9a
--- /dev/null
@@ -0,0 +1,344 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m $H $M
+
+This test tries two-way merge (aka fast forward with carry forward).
+
+There is the head (called H) and another commit (called M), which is
+simply ahead of H.  The index and the work tree contains a state that
+is derived from H, but may also have local changes.  This test checks
+all the combinations described in the two-tree merge "carry forward"
+rules, found in <Documentation/git-read-tree.txt>.
+
+In the test, these paths are used:
+        bozbar  - in H, stays in M, modified from bozbar to gnusto
+        frotz   - not in H added in M
+        nitfol  - in H, stays in M unmodified
+        rezrov  - in H, deleted in M
+        yomin   - not in H nor M
+'
+. ./test-lib.sh
+
+read_tree_twoway () {
+    git-read-tree -m "$1" "$2" && git-ls-files --stage
+}
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+compare_change () {
+       sed -n >current \
+           -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+           -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+           "$1"
+       diff -u expected current
+}
+
+check_cache_at () {
+       clean_if_empty=`git-diff-files -- "$1"`
+       case "$clean_if_empty" in
+       '')  echo "$1: clean" ;;
+       ?*)  echo "$1: dirty" ;;
+       esac
+       case "$2,$clean_if_empty" in
+       clean,)         :     ;;
+       clean,?*)       false ;;
+       dirty,)         false ;;
+       dirty,?*)       :     ;;
+       esac
+}
+
+cat >bozbar-old <<\EOF
+This is a sample file used in two-way fast forward merge
+tests.  Its second line ends with a magic word bozbar
+which will be modified by the merged head to gnusto.
+It has some extra lines so that external tools can
+successfully merge independent changes made to later
+lines (such as this one), avoiding line conflicts.
+EOF
+
+sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     cat bozbar-old >bozbar &&
+     echo rezrov >rezrov &&
+     echo yomin >yomin &&
+     git-update-index --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     cat bozbar-new >bozbar &&
+     git-update-index --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     git-diff-tree $treeH $treeM'
+
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     diff -u M.out 1-3.out &&
+     check_cache_at bozbar dirty &&
+     check_cache_at frotz dirty &&
+     check_cache_at nitfol dirty'
+
+echo '+100644 X 0      yomin' >expected
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     git-update-index --add yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >4.out || return 1
+     diff -u M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean'
+
+test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo yomin >yomin &&
+     git-update-index --add yomin &&
+     echo yomin yomin >yomin &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >5.out || return 1
+     diff -u M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty'
+
+test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     git-update-index --add frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff -u M.out 6.out &&
+     check_cache_at frotz clean'
+
+test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo frotz >frotz &&
+     git-update-index --add frotz &&
+     echo frotz frotz >frotz &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff -u M.out 7.out &&
+     check_cache_at frotz dirty'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-index --add frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo frotz frotz >frotz &&
+     git-update-index --add frotz &&
+     echo frotz >frotz &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     diff -u M.out 10.out'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo rezrov rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     echo rezrov >rezrov &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0    nitfol
++100644 X 0    nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-index --add nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >14.out || return 1
+     diff -u M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     check_cache_at nitfol clean'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo nitfol nitfol >nitfol &&
+     git-update-index --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >15.out || return 1
+     diff -u M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty'
+
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     echo bozbar bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-index --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff -u M.out 18.out &&
+     check_cache_at bozbar clean'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     cat bozbar-new >bozbar &&
+     git-update-index --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff -u M.out 19.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-index --add bozbar &&
+     read_tree_twoway $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff -u M.out 20.out &&
+     check_cache_at bozbar dirty'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     cat bozbar-old >bozbar &&
+     git-update-index --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# This fails with straight two-way fast forward.
+test_expect_success \
+    '22 - local change cache updated.' \
+    'rm -f .git/index &&
+     git-read-tree $treeH &&
+     git-checkout-index -u -f -q -a &&
+     sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
+     git-update-index --add bozbar &&
+     if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index &&
+     echo DF >DF &&
+     git-update-index --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-index --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test.' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-index --add DF &&
+     read_tree_twoway $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -u DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF dirty &&
+     :'
+
+test_done
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
new file mode 100755 (executable)
index 0000000..da3c813
--- /dev/null
@@ -0,0 +1,344 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m -u $H $M
+
+This is identical to t1001, but uses -u to update the work tree as well.
+
+'
+. ./test-lib.sh
+
+_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"
+compare_change () {
+       sed >current \
+           -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+           -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1"
+       diff -u expected current
+}
+
+check_cache_at () {
+       clean_if_empty=`git-diff-files -- "$1"`
+       case "$clean_if_empty" in
+       '')  echo "$1: clean" ;;
+       ?*)  echo "$1: dirty" ;;
+       esac
+       case "$2,$clean_if_empty" in
+       clean,)         :     ;;
+       clean,?*)       false ;;
+       dirty,)         false ;;
+       dirty,?*)       :     ;;
+       esac
+}
+
+test_expect_success \
+    setup \
+    'echo frotz >frotz &&
+     echo nitfol >nitfol &&
+     echo bozbar >bozbar &&
+     echo rezrov >rezrov &&
+     git-update-index --add nitfol bozbar rezrov &&
+     treeH=`git-write-tree` &&
+     echo treeH $treeH &&
+     git-ls-tree $treeH &&
+
+     echo gnusto >bozbar &&
+     git-update-index --add frotz bozbar --force-remove rezrov &&
+     git-ls-files --stage >M.out &&
+     treeM=`git-write-tree` &&
+     echo treeM $treeM &&
+     git-ls-tree $treeM &&
+     sum bozbar frotz nitfol >M.sum &&
+     git-diff-tree $treeH $treeM'
+
+test_expect_success \
+    '1, 2, 3 - no carry forward' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >1-3.out &&
+     cmp M.out 1-3.out &&
+     sum bozbar frotz nitfol >actual3.sum &&
+     cmp M.sum actual3.sum &&
+     check_cache_at bozbar clean &&
+     check_cache_at frotz clean &&
+     check_cache_at nitfol clean'
+
+test_expect_success \
+    '4 - carry forward local addition.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo "+100644 X 0 yomin" >expected &&
+     echo yomin >yomin &&
+     git-update-index --add yomin &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >4.out || return 1
+     diff -U0 M.out 4.out >4diff.out
+     compare_change 4diff.out expected &&
+     check_cache_at yomin clean &&
+     sum bozbar frotz nitfol >actual4.sum &&
+     cmp M.sum actual4.sum &&
+     echo yomin >yomin1 &&
+     diff yomin yomin1 &&
+     rm -f yomin1'
+
+test_expect_success \
+    '5 - carry forward local addition.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     git-read-tree -m -u $treeH &&
+     echo yomin >yomin &&
+     git-update-index --add yomin &&
+     echo yomin yomin >yomin &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >5.out || return 1
+     diff -U0 M.out 5.out >5diff.out
+     compare_change 5diff.out expected &&
+     check_cache_at yomin dirty &&
+     sum bozbar frotz nitfol >actual5.sum &&
+     cmp M.sum actual5.sum &&
+     : dirty index should have prevented -u from checking it out. &&
+     echo yomin yomin >yomin1 &&
+     diff yomin yomin1 &&
+     rm -f yomin1'
+
+test_expect_success \
+    '6 - local addition already has the same.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo frotz >frotz &&
+     git-update-index --add frotz &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >6.out &&
+     diff -U0 M.out 6.out &&
+     check_cache_at frotz clean &&
+     sum bozbar frotz nitfol >actual3.sum &&
+     cmp M.sum actual3.sum &&
+     echo frotz >frotz1 &&
+     diff frotz frotz1 &&
+     rm -f frotz1'
+
+test_expect_success \
+    '7 - local addition already has the same.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo frotz >frotz &&
+     git-update-index --add frotz &&
+     echo frotz frotz >frotz &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >7.out &&
+     diff -U0 M.out 7.out &&
+     check_cache_at frotz dirty &&
+     sum bozbar frotz nitfol >actual7.sum &&
+     if cmp M.sum actual7.sum; then false; else :; fi &&
+     : dirty index should have prevented -u from checking it out. &&
+     echo frotz frotz >frotz1 &&
+     diff frotz frotz1 &&
+     rm -f frotz1'
+
+test_expect_success \
+    '8 - conflicting addition.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo frotz frotz >frotz &&
+     git-update-index --add frotz &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '9 - conflicting addition.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo frotz frotz >frotz &&
+     git-update-index --add frotz &&
+     echo frotz >frotz &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '10 - path removed.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >10.out &&
+     cmp M.out 10.out &&
+     sum bozbar frotz nitfol >actual10.sum &&
+     cmp M.sum actual10.sum'
+
+test_expect_success \
+    '11 - dirty path removed.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     echo rezrov rezrov >rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '12 - unmatching local changes being removed.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo rezrov rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '13 - unmatching local changes being removed.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo rezrov rezrov >rezrov &&
+     git-update-index --add rezrov &&
+     echo rezrov >rezrov &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0    nitfol
++100644 X 0    nitfol
+EOF
+
+test_expect_success \
+    '14 - unchanged in two heads.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo nitfol nitfol >nitfol &&
+     git-update-index --add nitfol &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >14.out || return 1
+     diff -U0 M.out 14.out >14diff.out
+     compare_change 14diff.out expected &&
+     sum bozbar frotz >actual14.sum &&
+     grep -v nitfol M.sum > expected14.sum &&
+     cmp expected14.sum actual14.sum &&
+     sum bozbar frotz nitfol >actual14a.sum &&
+     if cmp M.sum actual14a.sum; then false; else :; fi &&
+     check_cache_at nitfol clean &&
+     echo nitfol nitfol >nitfol1 &&
+     diff nitfol nitfol1 &&
+     rm -f nitfol1'
+
+test_expect_success \
+    '15 - unchanged in two heads.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo nitfol nitfol >nitfol &&
+     git-update-index --add nitfol &&
+     echo nitfol nitfol nitfol >nitfol &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >15.out || return 1
+     diff -U0 M.out 15.out >15diff.out
+     compare_change 15diff.out expected &&
+     check_cache_at nitfol dirty &&
+     sum bozbar frotz >actual15.sum &&
+     grep -v nitfol M.sum > expected15.sum &&
+     cmp expected15.sum actual15.sum &&
+     sum bozbar frotz nitfol >actual15a.sum &&
+     if cmp M.sum actual15a.sum; then false; else :; fi &&
+     echo nitfol nitfol nitfol >nitfol1 &&
+     diff nitfol nitfol1 &&
+     rm -f nitfol1'
+
+test_expect_success \
+    '16 - conflicting local change.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo bozbar bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '17 - conflicting local change.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo bozbar bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     echo bozbar bozbar bozbar >bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+    '18 - local change already having a good result.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo gnusto >bozbar &&
+     git-update-index --add bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >18.out &&
+     diff -U0 M.out 18.out &&
+     check_cache_at bozbar clean &&
+     sum bozbar frotz nitfol >actual18.sum &&
+     cmp M.sum actual18.sum'
+
+test_expect_success \
+    '19 - local change already having a good result, further modified.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo gnusto >bozbar &&
+     git-update-index --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >19.out &&
+     diff -U0 M.out 19.out &&
+     check_cache_at bozbar dirty &&
+     sum frotz nitfol >actual19.sum &&
+     grep -v bozbar  M.sum > expected19.sum &&
+     cmp expected19.sum actual19.sum &&
+     sum bozbar frotz nitfol >actual19a.sum &&
+     if cmp M.sum actual19a.sum; then false; else :; fi &&
+     echo gnusto gnusto >bozbar1 &&
+     diff bozbar bozbar1 &&
+     rm -f bozbar1'
+
+test_expect_success \
+    '20 - no local change, use new tree.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     git-read-tree -m -u $treeH $treeM &&
+     git-ls-files --stage >20.out &&
+     diff -U0 M.out 20.out &&
+     check_cache_at bozbar clean &&
+     sum bozbar frotz nitfol >actual20.sum &&
+     cmp M.sum actual20.sum'
+
+test_expect_success \
+    '21 - no local change, dirty cache.' \
+    'rm -f .git/index nitfol bozbar rezrov frotz &&
+     git-read-tree --reset -u $treeH &&
+     echo bozbar >bozbar &&
+     git-update-index --add bozbar &&
+     echo gnusto gnusto >bozbar &&
+     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+    'DF vs DF/DF case setup.' \
+    'rm -f .git/index
+     echo DF >DF &&
+     git-update-index --add DF &&
+     treeDF=`git-write-tree` &&
+     echo treeDF $treeDF &&
+     git-ls-tree $treeDF &&
+
+     rm -f DF &&
+     mkdir DF &&
+     echo DF/DF >DF/DF &&
+     git-update-index --add --remove DF DF/DF &&
+     treeDFDF=`git-write-tree` &&
+     echo treeDFDF $treeDFDF &&
+     git-ls-tree $treeDFDF &&
+     git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+    'DF vs DF/DF case test.' \
+    'rm -f .git/index &&
+     rm -fr DF &&
+     echo DF >DF &&
+     git-update-index --add DF &&
+     git-read-tree -m -u $treeDF $treeDFDF &&
+     git-ls-files --stage >DFDFcheck.out &&
+     diff -U0 DFDF.out DFDFcheck.out &&
+     check_cache_at DF/DF clean'
+
+test_done
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
new file mode 100755 (executable)
index 0000000..19a0ed4
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-commit-tree options test
+
+This test checks that git-commit-tree can create a specific commit
+object by defining all environment variables that it understands.
+'
+
+. ./test-lib.sh
+
+cat >expected <<EOF
+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+author Author Name <author@email> 1117148400 +0000
+committer Committer Name <committer@email> 1117150200 +0000
+
+comment text
+EOF
+
+test_expect_success \
+    'test preparation: write empty tree' \
+    'git-write-tree >treeid'
+
+test_expect_success \
+    'construct commit' \
+    'echo comment text |
+     GIT_AUTHOR_NAME="Author Name" \
+     GIT_AUTHOR_EMAIL="author@email" \
+     GIT_AUTHOR_DATE="2005-05-26 23:00" \
+     GIT_COMMITTER_NAME="Committer Name" \
+     GIT_COMMITTER_EMAIL="committer@email" \
+     GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     TZ=GMT git-commit-tree `cat treeid` >commitid 2>/dev/null'
+
+test_expect_success \
+    'read commit' \
+    'git-cat-file commit `cat commitid` >commit'
+
+test_expect_success \
+    'compare commit' \
+    'diff expected commit'
+
+test_done
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
new file mode 100755 (executable)
index 0000000..c7db20e
--- /dev/null
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+
+echo "Hello World" > hello
+echo "Silly example" > example
+
+git-update-index --add hello example
+
+test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+
+echo "It's a new day for git" >>hello
+cat > diff.expect << EOF
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+EOF
+git-diff-files -p > diff.output
+test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff > diff.output
+test_expect_success 'git diff' 'cmp diff.expect diff.output'
+
+tree=$(git-write-tree 2>/dev/null)
+
+test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
+
+output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+
+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'
+
+git diff HEAD > diff.output
+test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+
+#rm hello
+#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+
+cat > whatchanged.expect << EOF
+commit VARIABLE
+Author: VARIABLE
+Date:   VARIABLE
+
+    Initial commit
+
+diff --git a/example b/example
+new file mode 100644
+index 0000000..f24c74a
+--- /dev/null
++++ b/example
+@@ -0,0 +1 @@
++Silly example
+diff --git a/hello b/hello
+new file mode 100644
+index 0000000..557db03
+--- /dev/null
++++ b/hello
+@@ -0,0 +1 @@
++Hello World
+EOF
+
+git-whatchanged -p --root | \
+       sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
+               -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
+> whatchanged.output
+test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+
+git tag my-first-tag
+test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+
+# TODO: test git-clone
+
+git checkout -b mybranch
+test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+
+cat > branch.expect <<EOF
+  master
+* mybranch
+EOF
+
+git branch > branch.output
+test_expect_success 'git branch' 'cmp branch.expect branch.output'
+
+git checkout mybranch
+echo "Work, work, work" >>hello
+git commit -m 'Some work.' -i hello
+
+git checkout master
+
+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"'
+
+cat > hello << EOF
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+EOF
+
+git commit -m 'Merged "mybranch" changes.' -i hello
+
+test_done
+
+cat > show-branch.expect << EOF
+* [master] Merged "mybranch" changes.
+ ! [mybranch] Some work.
+--
+-  [master] Merged "mybranch" changes.
+*+ [mybranch] Some work.
+EOF
+
+git show-branch --topo-order master mybranch > show-branch.output
+test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
+
+git checkout mybranch
+
+cat > resolve.expect << EOF
+Updating from VARIABLE to VARIABLE
+ example |    1 +
+ hello   |    1 +
+ 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
+test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+
+cat > show-branch2.expect << EOF
+! [master] Merged "mybranch" changes.
+ * [mybranch] Merged "mybranch" changes.
+--
+-- [master] Merged "mybranch" changes.
+EOF
+
+git show-branch --topo-order master mybranch > show-branch2.output
+test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+
+# TODO: test git fetch
+
+# TODO: test git push
+
+test_expect_success 'git repack' 'git repack'
+test_expect_success 'git prune-packed' 'git prune-packed'
+test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+
+test_done
+
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
new file mode 100755 (executable)
index 0000000..8260d57
--- /dev/null
@@ -0,0 +1,313 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-repo-config in different settings'
+
+. ./test-lib.sh
+
+test -f .git/config && rm .git/config
+
+git-repo-config core.penguin "little blue"
+
+cat > expect << EOF
+[core]
+       penguin = little blue
+EOF
+
+test_expect_success 'initial' 'cmp .git/config expect'
+
+git-repo-config Core.Movie BadPhysics
+
+cat > expect << EOF
+[core]
+       penguin = little blue
+       Movie = BadPhysics
+EOF
+
+test_expect_success 'mixed case' 'cmp .git/config expect'
+
+git-repo-config Cores.WhatEver Second
+
+cat > expect << EOF
+[core]
+       penguin = little blue
+       Movie = BadPhysics
+[Cores]
+       WhatEver = Second
+EOF
+
+test_expect_success 'similar section' 'cmp .git/config expect'
+
+git-repo-config CORE.UPPERCASE true
+
+cat > expect << EOF
+[core]
+       penguin = little blue
+       Movie = BadPhysics
+       UPPERCASE = true
+[Cores]
+       WhatEver = Second
+EOF
+
+test_expect_success 'similar section' 'cmp .git/config expect'
+
+test_expect_success 'replace with non-match' \
+       'git-repo-config core.penguin kingpin !blue'
+
+test_expect_success 'replace with non-match (actually matching)' \
+       'git-repo-config core.penguin "very blue" !kingpin'
+
+cat > expect << EOF
+[core]
+       penguin = very blue
+       Movie = BadPhysics
+       UPPERCASE = true
+       penguin = kingpin
+[Cores]
+       WhatEver = Second
+EOF
+
+test_expect_success 'non-match result' 'cmp .git/config expect'
+
+cat > .git/config << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+               haha   ="beta" # last silly comment
+haha = hello
+       haha = bello
+[nextSection] noNewline = ouch
+EOF
+
+cp .git/config .git/config2
+
+test_expect_success 'multiple unset' \
+       'git-repo-config --unset-all beta.haha'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection] noNewline = ouch
+EOF
+
+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'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+       haha = gamma
+[nextSection] noNewline = ouch
+EOF
+
+test_expect_success 'all replaced' 'cmp .git/config expect'
+
+git-repo-config beta.haha alpha
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+       haha = alpha
+[nextSection] noNewline = ouch
+EOF
+
+test_expect_success 'really mean test' 'cmp .git/config expect'
+
+git-repo-config nextsection.nonewline wow
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+       haha = alpha
+[nextSection]
+       nonewline = wow
+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
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection]
+       nonewline = wow
+EOF
+
+test_expect_success 'unset' 'cmp .git/config expect'
+
+git-repo-config nextsection.NoNewLine "wow2 for me" "for me$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection]
+       nonewline = wow
+       NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar' 'cmp .git/config expect'
+
+test_expect_success 'non-match' \
+       'git-repo-config --get nextsection.nonewline !for'
+
+test_expect_success 'non-match value' \
+       'test wow = $(git-repo-config --get nextsection.nonewline !for)'
+
+test_expect_failure 'ambiguous get' \
+       'git-repo-config --get nextsection.nonewline'
+
+test_expect_success 'get multivar' \
+       'git-repo-config --get-all nextsection.nonewline'
+
+git-repo-config nextsection.nonewline "wow3" "wow$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection]
+       nonewline = wow3
+       NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar replace' 'cmp .git/config expect'
+
+test_expect_failure 'ambiguous value' 'git-repo-config nextsection.nonewline'
+
+test_expect_failure 'ambiguous unset' \
+       'git-repo-config --unset nextsection.nonewline'
+
+test_expect_failure 'invalid unset' \
+       'git-repo-config --unset somesection.nonewline'
+
+git-repo-config --unset nextsection.nonewline "wow3$"
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection]
+       NoNewLine = wow2 for me
+EOF
+
+test_expect_success 'multivar unset' 'cmp .git/config expect'
+
+test_expect_failure 'invalid key' 'git-repo-config inval.2key blabla'
+
+test_expect_success 'correct key' 'git-repo-config 123456.a123 987'
+
+test_expect_success 'hierarchical section' \
+       'git-repo-config Version.1.2.3eX.Alpha beta'
+
+cat > expect << EOF
+[beta] ; silly comment # another comment
+noIndent= sillyValue ; 'nother silly comment
+
+# empty line
+               ; comment
+[nextSection]
+       NoNewLine = wow2 for me
+[123456]
+       a123 = 987
+[Version "1.2.3eX"]
+       Alpha = beta
+EOF
+
+test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+
+cat > expect << EOF
+beta.noindent=sillyValue
+nextsection.nonewline=wow2 for me
+123456.a123=987
+version.1.2.3eX.alpha=beta
+EOF
+
+test_expect_success 'working --list' \
+       'git-repo-config --list > output && cmp output expect'
+
+cat > expect << EOF
+beta.noindent sillyValue
+nextsection.nonewline wow2 for me
+EOF
+
+test_expect_success '--get-regexp' \
+       'git-repo-config --get-regexp in > output && cmp output expect'
+
+cat > .git/config << EOF
+[novalue]
+       variable
+EOF
+
+test_expect_success 'get variable with no value' \
+       'git-repo-config --get novalue.variable ^$'
+
+git-repo-config > output 2>&1
+
+test_expect_success 'no arguments, but no crash' \
+       "test $? = 129 && grep usage output"
+
+cat > .git/config << EOF
+[a.b]
+       c = d
+EOF
+
+git-repo-config a.x y
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+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
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+       b = c
+[b]
+       x = y
+EOF
+
+test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+
+test_done
+
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100755 (executable)
index 0000000..df3e993
--- /dev/null
@@ -0,0 +1,213 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+C=3333333333333333333333333333333333333333
+D=4444444444444444444444444444444444444444
+E=5555555555555555555555555555555555555555
+F=6666666666666666666666666666666666666666
+m=refs/heads/master
+
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m" \
+       'git-update-ref $m $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "create $m (by HEAD)" \
+       'git-update-ref HEAD $B $A &&
+        test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+       '(not) create HEAD with old sha1' \
+       'git-update-ref HEAD $A $B'
+test_expect_failure \
+       "(not) prior created .git/$m" \
+       'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+       "create HEAD" \
+       'git-update-ref HEAD $A'
+test_expect_failure \
+       '(not) change HEAD with wrong SHA1' \
+       'git-update-ref HEAD $B $Z'
+test_expect_failure \
+       "(not) changed .git/$m" \
+       'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+       "create $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by touch)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+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)'
+
+test_expect_success \
+       "create $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+        git-update-ref HEAD $A -m "Initial Creation" &&
+        test $A = $(cat .git/$m)'
+test_expect_success \
+       "update $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+        git-update-ref HEAD $B $A -m "Switch" &&
+        test $B = $(cat .git/$m)'
+test_expect_success \
+       "set $m (logged by config)" \
+       'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+        git-update-ref HEAD $A &&
+        test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000      Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000      Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+       "verifying $m's log" \
+       'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+git-update-ref $m $D
+cat >.git/logs/$m <<EOF
+$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+EOF
+
+ed="Thu, 26 May 2005 18:32:00 -0500"
+gd="Thu, 26 May 2005 18:33:00 -0500"
+ld="Thu, 26 May 2005 18:43:00 -0500"
+test_expect_success \
+       'Query "master@{May 25 2005}" (before history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+        test $C = $(cat o) &&
+        test "warning: Log .git/logs/$m 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)"'
+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_expect_success \
+       'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+        test $A = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+        test $B = $(cat o) &&
+        test "warning: Log .git/logs/$m has gap after $gd." = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+        test $Z = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+        test $E = $(cat o) &&
+        test "" = "$(cat e)"'
+test_expect_success \
+       'Query "master@{2005-05-28}" (past end of history)' \
+       'rm -f o e
+        git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+        test $D = $(cat o) &&
+        test "warning: Log .git/logs/$m unexpectedly ended on $ld." = "$(cat e)"'
+
+
+rm -f .git/$m .git/logs/$m expect
+
+test_expect_success \
+    'creating initial files' \
+    'echo TEST >F &&
+     git-add F &&
+        GIT_AUTHOR_DATE="2005-05-26 23:30" \
+        GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
+        h_TEST=$(git-rev-parse --verify HEAD)
+        echo The other day this did not work. >M &&
+        echo And then Bob told me how to fix it. >>M &&
+        echo OTHER >F &&
+        GIT_AUTHOR_DATE="2005-05-26 23:41" \
+        GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
+        h_OTHER=$(git-rev-parse --verify HEAD)
+        rm -f M'
+
+cat >expect <<EOF
+$Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit: add
+$h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000   commit: The other day this did not work.
+EOF
+test_expect_success \
+       'git-commit logged updates' \
+       'diff expect .git/logs/$m'
+unset h_TEST h_OTHER
+
+test_expect_success \
+       'git-cat-file blob master:F (expect OTHER)' \
+       'test OTHER = $(git-cat-file blob master:F)'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+       'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
+test_expect_success \
+       'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+       'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
+
+test_done
diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh
new file mode 100755 (executable)
index 0000000..03ea4de
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index test.
+
+This test registers the following filesystem structure in the
+cache:
+
+    path0       - a file
+    path1/file1 - a file in a directory
+
+And then tries to checkout in a work tree that has the following:
+
+    path0/file0 - a file in a directory
+    path1       - a file
+
+The git-checkout-index command should fail when attempting to checkout
+path0, finding it is occupied by a directory, and path1/file1, finding
+path1 is occupied by a non-directory.  With "-f" flag, it should remove
+the conflicting paths and succeed.
+'
+. ./test-lib.sh
+
+date >path0
+mkdir path1
+date >path1/file1
+
+test_expect_success \
+    'git-update-index --add various paths.' \
+    'git-update-index --add path0 path1/file1'
+
+rm -fr path0 path1
+mkdir path0
+date >path0/file0
+date >path1
+
+test_expect_failure \
+    'git-checkout-index without -f should fail on conflicting work tree.' \
+    'git-checkout-index -a'
+
+test_expect_success \
+    'git-checkout-index with -f should succeed.' \
+    'git-checkout-index -f -a'
+
+test_expect_success \
+    'git-checkout-index conflicting paths.' \
+    'test -f path0 && test -d path1 && test -f path1/file1'
+
+test_done
+
+
diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh
new file mode 100755 (executable)
index 0000000..0dcab8f
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index test.
+
+This test registers the following filesystem structure in the cache:
+
+    path0/file0        - a file in a directory
+    path1/file1 - a file in a directory
+
+and attempts to check it out when the work tree has:
+
+    path0/file0 - a file in a directory
+    path1       - a symlink pointing at "path0"
+
+Checkout cache should fail to extract path1/file1 because the leading
+path path1 is occupied by a non-directory.  With "-f" it should remove
+the symlink path1 and create directory path1 and file path1/file1.
+'
+. ./test-lib.sh
+
+show_files() {
+       # show filesystem files, just [-dl] for type and name
+       find path? -ls |
+       sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
+       # what's in the cache, just mode and name
+       git-ls-files --stage |
+       sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
+       # what's in the tree, just mode and name.
+       git-ls-tree -r "$1" |
+       sed -e 's/^\([0-9]*\)   [^ ]*   [0-9a-f]*       /tr: \1 /'
+}
+
+mkdir path0
+date >path0/file0
+test_expect_success \
+    'git-update-index --add path0/file0' \
+    'git-update-index --add path0/file0'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree1=$(git-write-tree)'
+test_debug 'show_files $tree1'
+
+mkdir path1
+date >path1/file1
+test_expect_success \
+    'git-update-index --add path1/file1' \
+    'git-update-index --add path1/file1'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree2=$(git-write-tree)'
+test_debug 'show_files $tree2'
+
+rm -fr path1
+test_expect_success \
+    'read previously written tree and checkout.' \
+    'git-read-tree -m $tree1 && git-checkout-index -f -a'
+test_debug 'show_files $tree1'
+
+ln -s path0 path1
+test_expect_success \
+    'git-update-index --add a symlink.' \
+    'git-update-index --add path1'
+test_expect_success \
+    'writing tree out with git-write-tree' \
+    'tree3=$(git-write-tree)'
+test_debug 'show_files $tree3'
+
+# Morten says "Got that?" here.
+# Test begins.
+
+test_expect_success \
+    'read previously written tree and checkout.' \
+    'git-read-tree $tree2 && git-checkout-index -f -a'
+test_debug 'show_files $tree2'
+
+test_expect_success \
+    'checking out conflicting path with -f' \
+    'test ! -h path0 && test -d path0 &&
+     test ! -h path1 && test -d path1 &&
+     test ! -h path0/file0 && test -f path0/file0 &&
+     test ! -h path1/file1 && test -f path1/file1'
+
+test_done
+
diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh
new file mode 100755 (executable)
index 0000000..4352ddb
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index -u test.
+
+With -u flag, git-checkout-index internally runs the equivalent of
+git-update-index --refresh on the checked out entry.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+echo frotz >path0 &&
+git-update-index --add path0 &&
+t=$(git-write-tree)'
+
+test_expect_failure \
+'without -u, git-checkout-index smudges stat information.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-index -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_expect_success \
+'with -u, git-checkout-index picks up stat information from new files.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-index -u -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_done
diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh
new file mode 100755 (executable)
index 0000000..f9bc90a
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index --prefix test.
+
+This test makes sure that --prefix option works as advertised, and
+also verifies that such leading path may contain symlinks, unlike
+the GIT controlled paths.
+'
+
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'mkdir path1 &&
+    echo frotz >path0 &&
+    echo rezrov >path1/file1 &&
+    git-update-index --add path0 path1/file1'
+
+test_expect_success \
+    'have symlink in place where dir is expected.' \
+    'rm -fr path0 path1 &&
+     mkdir path2 &&
+     ln -s path2 path1 &&
+     git-checkout-index -f -a &&
+     test ! -h path1 && test -d path1 &&
+     test -f path1/file1 && test ! -f path2/file1'
+
+test_expect_success \
+    'use --prefix=path2/' \
+    'rm -fr path0 path1 path2 &&
+     mkdir path2 &&
+     git-checkout-index --prefix=path2/ -f -a &&
+     test -f path2/path0 &&
+     test -f path2/path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+test_expect_success \
+    'use --prefix=tmp-' \
+    'rm -fr path0 path1 path2 tmp* &&
+     git-checkout-index --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test -f tmp-path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+test_expect_success \
+    'use --prefix=tmp- but with a conflicting file and dir' \
+    'rm -fr path0 path1 path2 tmp* &&
+     echo nitfol >tmp-path1 &&
+     mkdir tmp-path0 &&
+     git-checkout-index --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test -f tmp-path1/file1 &&
+     test ! -f path0 &&
+     test ! -f path1/file1'
+
+# Linus fix #1
+test_expect_success \
+    'use --prefix=tmp/orary/ where tmp is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 tmp1/orary &&
+     ln -s tmp1 tmp &&
+     git-checkout-index --prefix=tmp/orary/ -f -a &&
+     test -d tmp1/orary &&
+     test -f tmp1/orary/path0 &&
+     test -f tmp1/orary/path1/file1 &&
+     test -h tmp'
+
+# Linus fix #2
+test_expect_success \
+    'use --prefix=tmp/orary- where tmp is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 &&
+     ln -s tmp1 tmp &&
+     git-checkout-index --prefix=tmp/orary- -f -a &&
+     test -f tmp1/orary-path0 &&
+     test -f tmp1/orary-path1/file1 &&
+     test -h tmp'
+
+# Linus fix #3
+test_expect_success \
+    'use --prefix=tmp- where tmp-path1 is a symlink' \
+    'rm -fr path0 path1 path2 tmp* &&
+     mkdir tmp1 &&
+     ln -s tmp1 tmp-path1 &&
+     git-checkout-index --prefix=tmp- -f -a &&
+     test -f tmp-path0 &&
+     test ! -h tmp-path1 &&
+     test -d tmp-path1 &&
+     test -f tmp-path1/file1'
+
+test_done
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh
new file mode 100755 (executable)
index 0000000..c100959
--- /dev/null
@@ -0,0 +1,212 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='git-checkout-index --temp test.
+
+With --temp flag, git-checkout-index writes to temporary merge files
+rather than the tracked path.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+mkdir asubdir &&
+echo tree1path0 >path0 &&
+echo tree1path1 >path1 &&
+echo tree1path3 >path3 &&
+echo tree1path4 >path4 &&
+echo tree1asubdir/path5 >asubdir/path5 &&
+git-update-index --add path0 path1 path3 path4 asubdir/path5 &&
+t1=$(git-write-tree) &&
+rm -f path* .merge_* out .git/index &&
+echo tree2path0 >path0 &&
+echo tree2path1 >path1 &&
+echo tree2path2 >path2 &&
+echo tree2path4 >path4 &&
+git-update-index --add path0 path1 path2 path4 &&
+t2=$(git-write-tree) &&
+rm -f path* .merge_* out .git/index &&
+echo tree2path0 >path0 &&
+echo tree3path1 >path1 &&
+echo tree3path2 >path2 &&
+echo tree3path3 >path3 &&
+git-update-index --add path0 path1 path2 path3 &&
+t3=$(git-write-tree)'
+
+test_expect_success \
+'checkout one stage 0 to temporary file' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree $t1 &&
+git-checkout-index --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+p=$(cut "-d    " -f1 out) &&
+test -f $p &&
+test $(cat $p) = tree1path1'
+
+test_expect_success \
+'checkout all stage 0 to temporary files' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree $t1 &&
+git-checkout-index -a --temp >out &&
+test $(wc -l <out) = 5 &&
+for f in path0 path1 path3 path4 asubdir/path5
+do
+       test $(grep $f out | cut "-d    " -f2) = $f &&
+       p=$(grep $f out | cut "-d       " -f1) &&
+       test -f $p &&
+       test $(cat $p) = tree1$f
+done'
+
+test_expect_success \
+'prepare 3-way merge' '
+rm -f path* .merge_* out .git/index &&
+git-read-tree -m $t1 $t2 $t3'
+
+test_expect_success \
+'checkout one stage 2 to temporary file' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=2 --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+p=$(cut "-d    " -f1 out) &&
+test -f $p &&
+test $(cat $p) = tree2path1'
+
+test_expect_success \
+'checkout all stage 2 to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --all --stage=2 --temp >out &&
+test $(wc -l <out) = 3 &&
+for f in path1 path2 path4
+do
+       test $(grep $f out | cut "-d    " -f2) = $f &&
+       p=$(grep $f out | cut "-d       " -f1) &&
+       test -f $p &&
+       test $(cat $p) = tree2$f
+done'
+
+test_expect_success \
+'checkout all stages/one file to nothing' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path0 >out &&
+test $(wc -l <out) = 0'
+
+test_expect_success \
+'checkout all stages/one file to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path1 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path1 &&
+cut "-d        " -f1 out | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s1) = tree1path1 &&
+test $(cat $s2) = tree2path1 &&
+test $(cat $s3) = tree3path1)'
+
+test_expect_success \
+'checkout some stages/one file to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index --stage=all --temp -- path2 >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = path2 &&
+cut "-d        " -f1 out | (read s1 s2 s3 &&
+test $s1 = . &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s2) = tree2path2 &&
+test $(cat $s3) = tree3path2)'
+
+test_expect_success \
+'checkout all stages/all files to temporary files' '
+rm -f path* .merge_* out &&
+git-checkout-index -a --stage=all --temp >out &&
+test $(wc -l <out) = 5'
+
+test_expect_success \
+'-- path0: no entry' '
+test x$(grep path0 out | cut "-d       " -f2) = x'
+
+test_expect_success \
+'-- path1: all 3 stages' '
+test $(grep path1 out | cut "-d        " -f2) = path1 &&
+grep path1 out | cut "-d       " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s1) = tree1path1 &&
+test $(cat $s2) = tree2path1 &&
+test $(cat $s3) = tree3path1)'
+
+test_expect_success \
+'-- path2: no stage 1, have stage 2 and 3' '
+test $(grep path2 out | cut "-d        " -f2) = path2 &&
+grep path2 out | cut "-d       " -f1 | (read s1 s2 s3 &&
+test $s1 = . &&
+test -f $s2 &&
+test -f $s3 &&
+test $(cat $s2) = tree2path2 &&
+test $(cat $s3) = tree3path2)'
+
+test_expect_success \
+'-- path3: no stage 2, have stage 1 and 3' '
+test $(grep path3 out | cut "-d        " -f2) = path3 &&
+grep path3 out | cut "-d       " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test $s2 = . &&
+test -f $s3 &&
+test $(cat $s1) = tree1path3 &&
+test $(cat $s3) = tree3path3)'
+
+test_expect_success \
+'-- path4: no stage 3, have stage 1 and 3' '
+test $(grep path4 out | cut "-d        " -f2) = path4 &&
+grep path4 out | cut "-d       " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test -f $s2 &&
+test $s3 = . &&
+test $(cat $s1) = tree1path4 &&
+test $(cat $s2) = tree2path4)'
+
+test_expect_success \
+'-- asubdir/path5: no stage 2 and 3 have stage 1' '
+test $(grep asubdir/path5 out | cut "-d        " -f2) = asubdir/path5 &&
+grep asubdir/path5 out | cut "-d       " -f1 | (read s1 s2 s3 &&
+test -f $s1 &&
+test $s2 = . &&
+test $s3 = . &&
+test $(cat $s1) = tree1asubdir/path5)'
+
+test_expect_success \
+'checkout --temp within subdir' '
+(cd asubdir &&
+ git-checkout-index -a --stage=all >out &&
+ test $(wc -l <out) = 1 &&
+ test $(grep path5 out | cut "-d       " -f2) = path5 &&
+ grep path5 out | cut "-d      " -f1 | (read s1 s2 s3 &&
+ test -f ../$s1 &&
+ test $s2 = . &&
+ test $s3 = . &&
+ test $(cat ../$s1) = tree1asubdir/path5)
+)'
+
+test_expect_success \
+'checkout --temp symlink' '
+rm -f path* .merge_* out .git/index &&
+ln -s b a &&
+git-update-index --add a &&
+t4=$(git-write-tree) &&
+rm -f .git/index &&
+git-read-tree $t4 &&
+git-checkout-index --temp -a >out &&
+test $(wc -l <out) = 1 &&
+test $(cut "-d " -f2 out) = a &&
+p=$(cut "-d    " -f1 out) &&
+test -f $p &&
+test $(cat $p) = b'
+
+test_done
diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh
new file mode 100755 (executable)
index 0000000..5bc0a3b
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-update-index nonsense-path test.
+
+This test creates the following structure in the cache:
+
+    path0       - a file
+    path1       - a symlink
+    path2/file2 - a file in a directory
+    path3/file3 - a file in a directory
+
+and tries to git-update-index --add the following:
+
+    path0/file0 - a file in a directory
+    path1/file1 - a file in a directory
+    path2       - a file
+    path3       - a symlink
+
+All of the attempts should fail.
+'
+
+. ./test-lib.sh
+
+mkdir path2 path3
+date >path0
+ln -s xyzzy path1
+date >path2/file2
+date >path3/file3
+
+test_expect_success \
+    'git-update-index --add to add various paths.' \
+    'git-update-index --add -- path0 path1 path2/file2 path3/file3'
+
+rm -fr path?
+
+mkdir path0 path1
+date >path2
+ln -s frotz path3
+date >path0/file0
+date >path1/file1
+
+for p in path0/file0 path1/file1 path2 path3
+do
+       test_expect_failure \
+           "git-update-index to add conflicting path $p should fail." \
+           "git-update-index --add -- $p"
+done
+test_done
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
new file mode 100755 (executable)
index 0000000..a78ea7f
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git-update-index --again test.
+'
+
+. ./test-lib.sh
+
+cat > expected <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF
+test_expect_success 'update-index --add' \
+       'echo hello world >file1 &&
+        echo goodbye people >file2 &&
+        git-update-index --add file1 file2 &&
+        git-ls-files -s >current &&
+        cmp current expected'
+
+test_expect_success 'update-index --again' \
+       'rm -f file1 &&
+       echo hello everybody >file2 &&
+       if git-update-index --again
+       then
+               echo should have refused to remove file1
+               exit 1
+       else
+               echo happy - failed as expected
+       fi &&
+        git-ls-files -s >current &&
+        cmp current expected'
+
+cat > expected <<\EOF
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
+test_expect_success 'update-index --remove --again' \
+       'git-update-index --remove --again &&
+        git-ls-files -s >current &&
+        cmp current expected'
+
+test_expect_success 'first commit' 'git-commit -m initial'
+
+cat > expected <<\EOF
+100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
+test_expect_success 'update-index again' \
+       'mkdir -p dir1 &&
+       echo hello world >dir1/file3 &&
+       echo goodbye people >file2 &&
+       git-update-index --add file2 dir1/file3 &&
+       echo hello everybody >file2
+       echo happy >dir1/file3 &&
+       git-update-index --again &&
+       git-ls-files -s >current &&
+       cmp current expected'
+
+cat > expected <<\EOF
+100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
+test_expect_success 'update-index --update from subdir' \
+       'echo not so happy >file2 &&
+       cd dir1 &&
+       cat ../file2 >file3 &&
+       git-update-index --again &&
+       cd .. &&
+       git-ls-files -s >current &&
+       cmp current expected'
+
+cat > expected <<\EOF
+100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF
+test_expect_success 'update-index --update with pathspec' \
+       'echo very happy >file2 &&
+       cat file2 >dir1/file3 &&
+       git-update-index --again dir1/ &&
+       git-ls-files -s >current &&
+       cmp current expected'
+
+test_done
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
new file mode 100755 (executable)
index 0000000..adcbe03
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (--others should pick up symlinks).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+    path0       - a file
+    path1      - a symlink
+    path2/file2 - a file in a directory
+    path3-junk  - a file to confuse things
+    path3/file3 - a file in a directory
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+mkdir path2 path3
+date >path2/file2
+date >path2-junk
+date >path3/file3
+date >path3-junk
+git-update-index --add path3-junk path3/file3
+
+cat >expected1 <<EOF
+expected1
+expected2
+output
+path0
+path1
+path2-junk
+path2/file2
+EOF
+sed -e 's|path2/file2|path2/|' <expected1 >expected2
+
+test_expect_success \
+    'git-ls-files --others to show output.' \
+    'git-ls-files --others >output'
+
+test_expect_success \
+    'git-ls-files --others should pick up symlinks.' \
+    'diff output expected1'
+
+test_expect_success \
+    'git-ls-files --others --directory to show output.' \
+    'git-ls-files --others --directory >output'
+
+
+test_expect_success \
+    'git-ls-files --others --directory should not get confused.' \
+    'diff output expected2'
+
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
new file mode 100755 (executable)
index 0000000..6979b7c
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files --others --exclude
+
+This test runs git-ls-files --others and tests --exclude patterns.
+'
+
+. ./test-lib.sh
+
+rm -fr one three
+for dir in . one one/two three
+do
+  mkdir -p $dir &&
+  for i in 1 2 3 4 5 6 7 8
+  do
+    >$dir/a.$i
+  done
+done
+
+cat >expect <<EOF
+a.2
+a.4
+a.5
+a.8
+one/a.3
+one/a.4
+one/a.5
+one/a.7
+one/two/a.2
+one/two/a.3
+one/two/a.5
+one/two/a.7
+one/two/a.8
+three/a.2
+three/a.3
+three/a.4
+three/a.5
+three/a.8
+EOF
+
+echo '.gitignore
+output
+expect
+.gitignore
+*.7
+!*.8' >.git/ignore
+
+echo '*.1
+/*.3
+!*.6' >.gitignore
+echo '*.2
+two/*.4
+!*.7
+*.8' >one/.gitignore
+echo '!*.2
+!*.8' >one/two/.gitignore
+
+test_expect_success \
+    'git-ls-files --others with various exclude options.' \
+    'git-ls-files --others \
+       --exclude=\*.6 \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     diff -u expect output'
+
+# Test \r\n (MSDOS-like systems)
+printf '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
+
+test_expect_success \
+    'git-ls-files --others with \r\n line endings.' \
+    'git-ls-files --others \
+       --exclude=\*.6 \
+       --exclude-per-directory=.gitignore \
+       --exclude-from=.git/ignore \
+       >output &&
+     diff -u expect output'
+
+test_done
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
new file mode 100755 (executable)
index 0000000..b42f138
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (-- to terminate the path list).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+    path0       - a file
+    -foo       - a file with a funny name.
+    --         - another file with a funny name.
+'
+. ./test-lib.sh
+
+test_expect_success \
+       setup \
+       'echo frotz >path0 &&
+       echo frotz >./-foo &&
+       echo frotz >./--'
+
+test_expect_success \
+    'git-ls-files without path restriction.' \
+    'git-ls-files --others >output &&
+     diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction.' \
+    'git-ls-files --others path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with --.' \
+    'git-ls-files --others -- path0 >output &&
+       diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with path restriction with -- --.' \
+    'git-ls-files --others -- -- >output &&
+       diff -u output - <<EOF
+--
+EOF
+'
+
+test_expect_success \
+    'git-ls-files with no path restriction.' \
+    'git-ls-files --others -- >output &&
+       diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_done
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh
new file mode 100755 (executable)
index 0000000..5fc1976
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files -k and -m flags test.
+
+This test prepares the following in the cache:
+
+    path0       - a file
+    path1       - a symlink
+    path2/file2 - a file in a directory
+    path3/file3 - a file in a directory
+
+and the following on the filesystem:
+
+    path0/file0 - a file in a directory
+    path1/file1 - a file in a directory
+    path2       - a file
+    path3       - a symlink
+    path4      - a file
+    path5      - a symlink
+    path6/file6 - a file in a directory
+
+git-ls-files -k should report that existing filesystem
+objects except path4, path5 and path6/file6 to be killed.
+
+Also for modification test, the cache and working tree have:
+
+    path7       - an empty file, modified to a non-empty file.
+    path8       - a non-empty file, modified to an empty file.
+    path9      - an empty file, cache dirtied.
+    path10     - a non-empty file, cache dirtied.
+
+We should report path0, path1, path2/file2, path3/file3, path7 and path8
+modified without reporting path9 and path10.
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+mkdir path2 path3
+date >path2/file2
+date >path3/file3
+: >path7
+date >path8
+: >path9
+date >path10
+test_expect_success \
+    'git-update-index --add to add various paths.' \
+    "git-update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
+
+rm -fr path? ;# leave path10 alone
+date >path2
+ln -s frotz path3
+ln -s nitfol path5
+mkdir path0 path1 path6
+date >path0/file0
+date >path1/file1
+date >path6/file6
+date >path7
+: >path8
+: >path9
+touch path10
+
+test_expect_success \
+    'git-ls-files -k to show killed files.' \
+    'git-ls-files -k >.output'
+cat >.expected <<EOF
+path0/file0
+path1/file1
+path2
+path3
+EOF
+
+test_expect_success \
+    'validate git-ls-files -k output.' \
+    'diff .output .expected'
+
+test_expect_success \
+    'git-ls-files -m to show modified files.' \
+    'git-ls-files -m >.output'
+cat >.expected <<EOF
+path0
+path1
+path2/file2
+path3/file3
+path7
+path8
+EOF
+
+test_expect_success \
+    'validate git-ls-files -m output.' \
+    'diff .output .expected'
+
+test_done
diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh
new file mode 100755 (executable)
index 0000000..d55559e
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='git-ls-files test for --error-unmatch option
+
+This test runs git-ls-files --error-unmatch to ensure it correctly
+returns an error when a non-existent path is provided on the command
+line.
+'
+. ./test-lib.sh
+
+touch foo bar
+git-update-index --add foo bar
+git-commit -m "add foo bar"
+
+test_expect_failure \
+    'git-ls-files --error-unmatch should fail with unmatched path.' \
+    'git-ls-files --error-unmatch foo bar-does-not-match'
+
+test_expect_success \
+    'git-ls-files --error-unmatch should succeed eith matched paths.' \
+    'git-ls-files --error-unmatch foo bar'
+
+test_done
+1
diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
new file mode 100755 (executable)
index 0000000..2ec06d3
--- /dev/null
@@ -0,0 +1,158 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-tree test.
+
+This test runs git-ls-tree with the following in a tree.
+
+    path0       - a file
+    path1      - a symlink
+    path2/foo   - a file in a directory
+    path2/bazbo - a symlink in a directory
+    path2/baz/b - a file in a directory in a directory
+
+The new path restriction code should do the right thing for path2 and
+path2/baz.  Also path0/ should snow nothing.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'mkdir path2 path2/baz &&
+     echo Hi >path0 &&
+     ln -s path0 path1 &&
+     echo Lo >path2/foo &&
+     ln -s ../path1 path2/bazbo &&
+     echo Mi >path2/baz/b &&
+     find path? \( -type f -o -type l \) -print |
+     xargs git-update-index --add &&
+     tree=`git-write-tree` &&
+     echo $tree'
+
+_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"
+test_output () {
+    sed -e "s/ $_x40   / X     /" <current >check
+    diff -u expected check
+}
+
+test_expect_success \
+    'ls-tree plain' \
+    'git-ls-tree $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path0
+120000 blob X  path1
+040000 tree X  path2
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree recursive' \
+    'git-ls-tree -r $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path0
+120000 blob X  path1
+100644 blob X  path2/baz/b
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree recursive with -t' \
+    'git-ls-tree -r -t $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path0
+120000 blob X  path1
+040000 tree X  path2
+040000 tree X  path2/baz
+100644 blob X  path2/baz/b
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree recursive with -d' \
+    'git-ls-tree -r -d $tree >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+040000 tree X  path2/baz
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path' \
+    'git-ls-tree $tree path >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+
+# it used to be path1 and then path0, but with pathspec semantics
+# they are shown in canonical order.
+test_expect_success \
+    'ls-tree filtered with path1 path0' \
+    'git-ls-tree $tree path1 path0 >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path0
+120000 blob X  path1
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path0/' \
+    'git-ls-tree $tree path0/ >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+# It used to show path2 and its immediate children but
+# with pathspec semantics it shows only path2
+test_expect_success \
+    'ls-tree filtered with path2' \
+    'git-ls-tree $tree path2 >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+EOF
+     test_output'
+
+# ... and path2/ shows the children.
+test_expect_success \
+    'ls-tree filtered with path2/' \
+    'git-ls-tree $tree path2/ >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2/baz
+120000 blob X  path2/bazbo
+100644 blob X  path2/foo
+EOF
+     test_output'
+
+# The same change -- exact match does not show children of
+# path2/baz
+test_expect_success \
+    'ls-tree filtered with path2/baz' \
+    'git-ls-tree $tree path2/baz >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2/baz
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filtered with path2/bak' \
+    'git-ls-tree $tree path2/bak >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree -t filtered with path2/bak' \
+    'git-ls-tree -t $tree path2/bak >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path2
+EOF
+     test_output'
+
+test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
new file mode 100755 (executable)
index 0000000..d78deb1
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-ls-tree directory and filenames handling.
+
+This test runs git-ls-tree with the following in a tree.
+
+    1.txt              - a file
+    2.txt              - a file
+    path0/a/b/c/1.txt  - a file in a directory
+    path1/b/c/1.txt    - a file in a directory
+    path2/1.txt        - a file in a directory
+    path3/1.txt        - a file in a directory
+    path3/2.txt        - a file in a directory
+
+Test the handling of mulitple directories which have matching file
+entries.  Also test odd filename and missing entries handling.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'echo 111 >1.txt &&
+     echo 222 >2.txt &&
+     mkdir path0 path0/a path0/a/b path0/a/b/c &&
+     echo 111 >path0/a/b/c/1.txt &&
+     mkdir path1 path1/b path1/b/c &&
+     echo 111 >path1/b/c/1.txt &&
+     mkdir path2 &&
+     echo 111 >path2/1.txt &&
+     mkdir path3 &&
+     echo 111 >path3/1.txt &&
+     echo 222 >path3/2.txt &&
+     find *.txt path* \( -type f -o -type l \) -print |
+     xargs git-update-index --add &&
+     tree=`git-write-tree` &&
+     echo $tree'
+
+_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"
+test_output () {
+    sed -e "s/ $_x40   / X     /" <current >check
+    diff -u expected check
+}
+
+test_expect_success \
+    'ls-tree plain' \
+    'git-ls-tree $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  2.txt
+040000 tree X  path0
+040000 tree X  path1
+040000 tree X  path2
+040000 tree X  path3
+EOF
+     test_output'
+
+# Recursive does not show tree nodes anymore...
+test_expect_success \
+    'ls-tree recursive' \
+    'git-ls-tree -r $tree >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  2.txt
+100644 blob X  path0/a/b/c/1.txt
+100644 blob X  path1/b/c/1.txt
+100644 blob X  path2/1.txt
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter 1.txt' \
+    'git-ls-tree $tree 1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter path1/b/c/1.txt' \
+    'git-ls-tree $tree path1/b/c/1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  path1/b/c/1.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter all 1.txt files' \
+    'git-ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  path0/a/b/c/1.txt
+100644 blob X  path1/b/c/1.txt
+100644 blob X  path2/1.txt
+100644 blob X  path3/1.txt
+EOF
+     test_output'
+
+# I am not so sure about this one after ls-tree doing pathspec match.
+# Having both path0/a and path0/a/b/c makes path0/a redundant, and
+# it behaves as if path0/a/b/c, path1/b/c, path2 and path3 are specified.
+test_expect_success \
+    'ls-tree filter directories' \
+    'git-ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+     cat >expected <<\EOF &&
+040000 tree X  path0/a/b/c
+040000 tree X  path1/b/c
+040000 tree X  path2
+040000 tree X  path3
+EOF
+     test_output'
+
+# Again, duplicates are filtered away so this is equivalent to
+# having 1.txt and path3
+test_expect_success \
+    'ls-tree filter odd names' \
+    'git-ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+     cat >expected <<\EOF &&
+100644 blob X  1.txt
+100644 blob X  path3/1.txt
+100644 blob X  path3/2.txt
+EOF
+     test_output'
+
+test_expect_success \
+    'ls-tree filter missing files and extra slashes' \
+    'git-ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
+     cat >expected <<\EOF &&
+EOF
+     test_output'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
new file mode 100755 (executable)
index 0000000..5b04efc
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+#
+
+test_description='git branch --foo should not create bogus branch
+
+This test runs git branch --help and checks that the argument is properly
+handled.  Specifically, that a bogus branch is not created.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'prepare an trivial repository' \
+    'echo Hello > A &&
+     git-update-index --add A &&
+     git-commit -m "Initial commit." &&
+     HEAD=$(git-rev-parse --verify HEAD)'
+
+test_expect_success \
+    'git branch --help should return success now.' \
+    'git-branch --help'
+
+test_expect_failure \
+    'git branch --help should not have created a bogus branch' \
+    'test -f .git/refs/heads/--help'
+
+test_expect_success \
+    'git branch abc should create a branch' \
+    'git-branch abc && test -f .git/refs/heads/abc'
+
+test_expect_success \
+    'git branch a/b/c should create a branch' \
+    'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
+
+cat >expect <<EOF
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from HEAD
+EOF
+test_expect_success \
+    'git branch -l d/e/f should create a branch and a log' \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     git-branch -l d/e/f &&
+        test -f .git/refs/heads/d/e/f &&
+        test -f .git/logs/refs/heads/d/e/f &&
+        diff expect .git/logs/refs/heads/d/e/f'
+
+test_expect_success \
+    'git branch -d d/e/f should delete a branch and a log' \
+       'git-branch -d d/e/f &&
+        test ! -f .git/refs/heads/d/e/f &&
+        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
+EOF
+test_expect_success \
+    'git checkout -b g/h/i -l should create a branch and a log' \
+       'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+     git-checkout -b g/h/i -l master &&
+        test -f .git/refs/heads/g/h/i &&
+        test -f .git/logs/refs/heads/g/h/i &&
+        diff expect .git/logs/refs/heads/g/h/i'
+
+test_done
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
new file mode 100755 (executable)
index 0000000..c12270e
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathnames with funny characters.
+
+This test tries pathnames with funny characters in the working
+tree, index, and tree objects.
+'
+
+. ./test-lib.sh
+
+p0='no-funny'
+p1='tabs       ," (dq) and spaces'
+p2='just space'
+
+cat >"$p0" <<\EOF
+1. A quick brown fox jumps over the lazy cat, oops dog.
+2. A quick brown fox jumps over the lazy cat, oops dog.
+3. A quick brown fox jumps over the lazy cat, oops dog.
+EOF
+
+cat >"$p1" "$p0"
+echo 'Foo Bar Baz' >"$p2"
+
+test -f "$p1" && cmp "$p0" "$p1" || {
+       # since FAT/NTFS does not allow tabs in filenames, skip this test
+       say 'Your filesystem does not allow tabs in filenames, test skipped.'
+       test_done
+}
+
+echo 'just space
+no-funny' >expected
+test_expect_success 'git-ls-files no-funny' \
+       'git-update-index --add "$p0" "$p2" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+t0=`git-write-tree`
+echo "$t0" >t0
+
+cat > expected <<\EOF
+just space
+no-funny
+"tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-ls-files with-funny' \
+       'git-update-index --add "$p1" &&
+       git-ls-files >current &&
+       diff -u expected current'
+
+echo 'just space
+no-funny
+tabs   ," (dq) and spaces' >expected
+test_expect_success 'git-ls-files -z with-funny' \
+       'git-ls-files -z | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+t1=`git-write-tree`
+echo "$t1" >t1
+
+cat > expected <<\EOF
+just space
+no-funny
+"tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-ls-tree with funny' \
+       'git-ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
+        diff -u expected current'
+
+cat > expected <<\EOF
+A      "tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-diff-index with-funny' \
+       'git-diff-index --name-status $t0 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree with-funny' \
+       'git-diff-tree --name-status $t0 $t1 >current &&
+       diff -u expected current'
+
+echo 'A
+tabs   ," (dq) and spaces' >expected
+test_expect_success 'git-diff-index -z with-funny' \
+       'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+test_expect_success 'git-diff-tree -z with-funny' \
+       'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+       diff -u expected current'
+
+cat > expected <<\EOF
+CNUM   no-funny        "tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-diff-tree -C with-funny' \
+       'git-diff-tree -C --find-copies-harder --name-status \
+               $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
+       diff -u expected current'
+
+cat > expected <<\EOF
+RNUM   no-funny        "tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-update-index --force-remove "$p0" &&
+       git-diff-index -M --name-status \
+               $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
+       diff -u expected current'
+
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+similarity index NUM%
+rename from no-funny
+rename to "tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+chmod +x "$p1"
+cat > expected <<\EOF
+diff --git a/no-funny "b/tabs\t,\" (dq) and spaces"
+old mode 100644
+new mode 100755
+similarity index NUM%
+rename from no-funny
+rename to "tabs\t,\" (dq) and spaces"
+EOF
+test_expect_success 'git-diff-tree delete with-funny' \
+       'git-diff-index -M -p $t0 |
+        sed -e "s/index [0-9]*%/index NUM%/" >current &&
+        diff -u expected current'
+
+cat >expected <<\EOF
+ "tabs\t,\" (dq) and spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)
+EOF
+test_expect_success 'git-diff-tree rename with-funny applied' \
+       'git-diff-index -M -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+cat > expected <<\EOF
+ no-funny
+ "tabs\t,\" (dq) and spaces"
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+EOF
+test_expect_success 'git-diff-tree delete with-funny applied' \
+       'git-diff-index -p $t0 |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_expect_success 'git-apply non-git diff' \
+       'git-diff-index -p $t0 |
+        sed -ne "/^[-+@]/p" |
+        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        diff -u expected current'
+
+test_done
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
new file mode 100755 (executable)
index 0000000..b9d3131
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+#
+
+test_description='git rebase should not destroy author information
+
+This test runs git rebase and checks that the author information is not lost.
+'
+. ./test-lib.sh
+
+export GIT_AUTHOR_EMAIL=bogus_email_address
+
+test_expect_success \
+    'prepare repository with topic branch, then rebase against master' \
+    'echo First > A &&
+     git-update-index --add A &&
+     git-commit -m "Add A." &&
+     git checkout -b my-topic-branch &&
+     echo Second > B &&
+     git-update-index --add B &&
+     git-commit -m "Add B." &&
+     git checkout -f master &&
+     echo Third >> A &&
+     git-update-index A &&
+     git-commit -m "Modify A." &&
+     git checkout -f my-topic-branch &&
+     git rebase master'
+
+test_expect_failure \
+    'the rebase operation should not have destroyed author information' \
+    'git log | grep "Author:" | grep "<>"'
+
+test_done
diff --git a/t/t3401-rebase-partial.sh b/t/t3401-rebase-partial.sh
new file mode 100755 (executable)
index 0000000..32dc9c5
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
+#
+
+test_description='git rebase should detect patches integrated upstream
+
+This test cherry-picks one local change of two into master branch, and
+checks that git rebase succeeds with only the second patch in the
+local branch.
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'prepare repository with topic branch' \
+    'echo First > A &&
+     git-update-index --add A &&
+     git-commit -m "Add A." &&
+
+     git-checkout -b my-topic-branch &&
+
+     echo Second > B &&
+     git-update-index --add B &&
+     git-commit -m "Add B." &&
+
+     echo AnotherSecond > C &&
+     git-update-index --add C &&
+     git-commit -m "Add C." &&
+
+     git-checkout -f master &&
+
+     echo Third >> A &&
+     git-update-index A &&
+     git-commit -m "Modify A."
+'
+
+test_expect_success \
+    'pick top patch from topic branch into master' \
+    'git-cherry-pick my-topic-branch^0 &&
+     git-checkout -f my-topic-branch
+'
+
+test_debug \
+    'git-cherry master &&
+     git-format-patch -k --stdout --full-index master >/dev/null &&
+     gitk --all & sleep 1
+'
+
+test_expect_success \
+    'rebase topic branch against new master and check git-am did not get halted' \
+    'git-rebase master && test ! -d .dotest'
+
+test_done
diff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh
new file mode 100755 (executable)
index 0000000..e83bbee
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
+#
+
+test_description='git-cherry should detect patches integrated upstream
+
+This test cherry-picks one local change of two into master branch, and
+checks that git-cherry only returns the second patch in the local branch
+'
+. ./test-lib.sh
+
+export GIT_AUTHOR_EMAIL=bogus_email_address
+
+test_expect_success \
+    'prepare repository with topic branch, and check cherry finds the 2 patches from there' \
+    'echo First > A &&
+     git-update-index --add A &&
+     git-commit -m "Add A." &&
+
+     git-checkout -b my-topic-branch &&
+
+     echo Second > B &&
+     git-update-index --add B &&
+     git-commit -m "Add B." &&
+
+     sleep 2 &&
+     echo AnotherSecond > C &&
+     git-update-index --add C &&
+     git-commit -m "Add C." &&
+
+     git-checkout -f master &&
+     rm -f B C &&
+
+     echo Third >> A &&
+     git-update-index A &&
+     git-commit -m "Modify A." &&
+
+     expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* + .*"
+'
+
+test_expect_success \
+    'check that cherry with limit returns only the top patch'\
+    'expr "$(echo $(git-cherry master my-topic-branch my-topic-branch^1) )" : "+ [^ ]*"
+'
+
+test_expect_success \
+    'cherry-pick one of the 2 patches, and check cherry recognized one and only one as new' \
+    'git-cherry-pick my-topic-branch^0 &&
+     echo $(git-cherry master my-topic-branch) &&
+     expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* - .*"
+'
+
+test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
new file mode 100755 (executable)
index 0000000..201d164
--- /dev/null
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='Test of the various options to git-rm.'
+
+. ./test-lib.sh
+
+# Setup some files to be removed, some with funny characters
+test_expect_success \
+    'Initialize test directory' \
+    "touch -- foo bar baz 'space embedded' -q &&
+     git-add -- foo bar baz 'space embedded' -q &&
+     git-commit -m 'add normal files' &&
+     test_tabs=y &&
+     if touch -- 'tab  embedded' 'newline
+embedded'
+     then
+     git-add -- 'tab   embedded' 'newline
+embedded' &&
+     git-commit -m 'add files with tabs and newlines'
+     else
+         say 'Your filesystem does not allow tabs in filenames.'
+         test_tabs=n
+     fi"
+
+# Later we will try removing an unremovable path to make sure
+# git-rm barfs, but if the test is run as root that cannot be
+# arranged.
+test_expect_success \
+    'Determine rm behavior' \
+    ': >test-file
+     chmod a-w .
+     rm -f test-file
+     test -f test-file && test_failed_remove=y
+     chmod 775 .
+     rm -f test-file'
+
+test_expect_success \
+    'Pre-check that foo exists and is in index before git-rm foo' \
+    '[ -f foo ] && git-ls-files --error-unmatch foo'
+
+test_expect_success \
+    'Test that git-rm foo succeeds' \
+    'git-rm 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"' \
+    '[ -f bar ] && git-ls-files --error-unmatch bar'
+
+test_expect_success \
+    'Test that "git-rm -f bar" succeeds' \
+    'git-rm -f bar'
+
+test_expect_success \
+    'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \
+    '! [ -f bar ] && ! git-ls-files --error-unmatch bar'
+
+test_expect_success \
+    'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
+    'git-rm -- -q'
+
+test "$test_tabs" = y && test_expect_success \
+    "Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
+    "git-rm -f 'space embedded' 'tab   embedded' 'newline
+embedded'"
+
+if test "$test_failed_remove" = y; then
+chmod a-w .
+test_expect_failure \
+    'Test that "git-rm -f" fails if its rm fails' \
+    'git-rm -f baz'
+chmod 775 .
+else
+    test_expect_success 'skipping removal failure (perhaps running as root?)' :
+fi
+
+test_expect_success \
+    'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
+    'git-ls-files --error-unmatch baz'
+
+test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh
new file mode 100755 (executable)
index 0000000..6cd05c3
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Carl D. Worth
+#
+
+test_description='Test of git-add, including the -- option.'
+
+. ./test-lib.sh
+
+test_expect_success \
+    'Test of git-add' \
+    'touch foo && git-add foo'
+
+test_expect_success \
+    'Post-check that foo is in the index' \
+    'git-ls-files foo | grep foo'
+
+test_expect_success \
+    'Test that "git-add -- -q" works' \
+    'touch -- -q && git-add -- -q'
+
+test_done
diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh
new file mode 100755 (executable)
index 0000000..67b9681
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test built-in diff output engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+line 3'
+cat path0 >path1
+chmod +x path1
+
+test_expect_success \
+    'update-cache --add two files with and without +x.' \
+    'git-update-index --add path0 path1'
+
+mv path0 path0-
+sed -e 's/line/Line/' <path0- >path0
+chmod +x path0
+rm -f path1
+test_expect_success \
+    'git-diff-files -p after editing work tree.' \
+    'git-diff-files -p >current'
+
+# that's as far as it comes
+if [ "$(git repo-config --get core.filemode)" = false ]
+then
+       say 'filemode disabled on the filesystem'
+       test_done
+fi
+
+cat >expected <<\EOF
+diff --git a/path0 b/path0
+old mode 100644
+new mode 100755
+--- a/path0
++++ b/path0
+@@ -1,3 +1,3 @@
+ Line 1
+ Line 2
+-line 3
++Line 3
+diff --git a/path1 b/path1
+deleted file mode 100755
+--- a/path1
++++ /dev/null
+@@ -1,3 +0,0 @@
+-Line 1
+-Line 2
+-line 3
+EOF
+
+test_expect_success \
+    'validate git-diff-files -p output.' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
new file mode 100755 (executable)
index 0000000..2e3c20d
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test rename detection in diff engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6
+Line 7
+Line 8
+Line 9
+Line 10
+line 11
+Line 12
+Line 13
+Line 14
+Line 15
+'
+
+test_expect_success \
+    'update-cache --add a file.' \
+    'git-update-index --add path0'
+
+test_expect_success \
+    'write that tree.' \
+    'tree=$(git-write-tree) && echo $tree'
+
+sed -e 's/line/Line/' <path0 >path1
+rm -f path0
+test_expect_success \
+    'renamed and edited the file.' \
+    'git-update-index --add --remove path0 path1'
+
+test_expect_success \
+    'git-diff-index -p -M after rename and editing.' \
+    'git-diff-index -p -M $tree >current'
+cat >expected <<\EOF
+diff --git a/path0 b/path1
+rename from path0
+rename to path1
+--- a/path0
++++ b/path1
+@@ -8,7 +8,7 @@ Line 7
+ Line 8
+ Line 9
+ Line 10
+-line 11
++Line 11
+ Line 12
+ Line 13
+ Line 14
+EOF
+
+test_expect_success \
+    'validate the output.' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh
new file mode 100755 (executable)
index 0000000..56eda63
--- /dev/null
@@ -0,0 +1,247 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test diff raw-output.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+cat >.test-plain-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 040000 0000000000000000000000000000000000000000 6d50f65d3bdab91c63444294d38f08aeff328e42 A     DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M     MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M     MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M     TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 5e5f22072bb39f6e12cf663a57cb634c76eefb49 M     Z
+EOF
+
+cat >.test-recursive-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A     AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A     AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 A     DF/DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D     DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D     DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M     MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M     MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M     TT
+:000000 100644 0000000000000000000000000000000000000000 8acb8e9750e3f644bf323fcf3d338849db106c77 A     Z/AA
+:000000 100644 0000000000000000000000000000000000000000 087494262084cefee7ed484d20c8dc0580791272 A     Z/AN
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D     Z/DD
+:100644 000000 9b541b2275c06e3a7b13f28badf5294e2ae63df4 0000000000000000000000000000000000000000 D     Z/DM
+:100644 000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a 0000000000000000000000000000000000000000 D     Z/DN
+:100644 100644 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 M     Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 61422ba9c2c873416061a88cd40a59a35b576474 M     Z/MM
+:100644 100644 b16d7b25b869f2beb124efa53467d8a1550ad694 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd M     Z/MN
+EOF
+cat >.test-plain-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 1ba523955d5160681af65cb776411f574c1e8155 M     Z
+EOF
+cat >.test-recursive-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A     AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D     DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M     DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A     LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M     SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:000000 100644 0000000000000000000000000000000000000000 6c0b99286d0bce551ac4a7b3dff8b706edff3715 A     Z/AA
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D     Z/DD
+:100644 100644 9b541b2275c06e3a7b13f28badf5294e2ae63df4 d77371d15817fcaa57eeec27f770c505ba974ec1 M     Z/DM
+:100644 000000 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 0000000000000000000000000000000000000000 D     Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 697aad7715a1e7306ca76290a3dd4208fbaeddfa M     Z/MM
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A     Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D     Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M     Z/NM
+EOF
+cat >.test-plain-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M     AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D     AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:040000 000000 6d50f65d3bdab91c63444294d38f08aeff328e42 0000000000000000000000000000000000000000 D     DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:040000 040000 5e5f22072bb39f6e12cf663a57cb634c76eefb49 1ba523955d5160681af65cb776411f574c1e8155 M     Z
+EOF
+cat >.test-recursive-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M     AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D     AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A     DF
+:100644 000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 0000000000000000000000000000000000000000 D     DF/DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A     DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A     DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D     MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M     MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M     MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A     NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D     ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M     NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M     TT
+:100644 100644 8acb8e9750e3f644bf323fcf3d338849db106c77 6c0b99286d0bce551ac4a7b3dff8b706edff3715 M     Z/AA
+:100644 000000 087494262084cefee7ed484d20c8dc0580791272 0000000000000000000000000000000000000000 D     Z/AN
+:000000 100644 0000000000000000000000000000000000000000 d77371d15817fcaa57eeec27f770c505ba974ec1 A     Z/DM
+:000000 100644 0000000000000000000000000000000000000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a A     Z/DN
+:100644 000000 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 0000000000000000000000000000000000000000 D     Z/MD
+:100644 100644 61422ba9c2c873416061a88cd40a59a35b576474 697aad7715a1e7306ca76290a3dd4208fbaeddfa M     Z/MM
+:100644 100644 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd b16d7b25b869f2beb124efa53467d8a1550ad694 M     Z/MN
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A     Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D     Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M     Z/NM
+EOF
+
+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"
+z40='0000000000000000000000000000000000000000'
+cmp_diff_files_output () {
+    # diff-files never reports additions.  Also it does not fill in the
+    # object ID for the changed files because it wants you to look at the
+    # filesystem.
+    sed <"$2" >.test-tmp \
+       -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\)    /'$z40'\1       /' &&
+    diff "$1" .test-tmp
+}
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_O $tree_A >.test-a &&
+     cmp -s .test-a .test-plain-OA'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_O $tree_A >.test-a &&
+     cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_O $tree_B >.test-a &&
+     cmp -s .test-a .test-plain-OB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_O $tree_B >.test-a &&
+     cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree $tree_A $tree_B >.test-a &&
+     cmp -s .test-a .test-plain-AB'
+
+test_expect_success \
+    'diff-tree of known trees.' \
+    'git-diff-tree -r $tree_A $tree_B >.test-a &&
+     cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+    'diff-cache O with A in cache' \
+    'git-read-tree $tree_A &&
+     git-diff-index --cached $tree_O >.test-a &&
+     cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-cache O with B in cache' \
+    'git-read-tree $tree_B &&
+     git-diff-index --cached $tree_O >.test-a &&
+     cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-cache A with B in cache' \
+    'git-read-tree $tree_B &&
+     git-diff-index --cached $tree_A >.test-a &&
+     cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+    'diff-files with O in cache and A checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_A &&
+     git-checkout-index -f -a &&
+     git-read-tree --reset $tree_O || return 1
+     git-update-index --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-OA'
+
+test_expect_success \
+    'diff-files with O in cache and B checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_B &&
+     git-checkout-index -f -a &&
+     git-read-tree --reset $tree_O || return 1
+     git-update-index --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-OB'
+
+test_expect_success \
+    'diff-files with A in cache and B checked out' \
+    'rm -fr Z [A-Z][A-Z] &&
+     git-read-tree $tree_B &&
+     git-checkout-index -f -a &&
+     git-read-tree --reset $tree_A || return 1
+     git-update-index --refresh >/dev/null ;# this can exit non-zero
+     git-diff-files >.test-a &&
+     cmp_diff_files_output .test-a .test-recursive-AB'
+
+################################################################
+# Now we have established the baseline, we do not have to
+# rely on individual object ID values that much.
+
+test_expect_success \
+    'diff-tree O A == diff-tree -R A O' \
+    'git-diff-tree $tree_O $tree_A >.test-a &&
+    git-diff-tree -R $tree_A $tree_O >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree -r O A == diff-tree -r -R A O' \
+    'git-diff-tree -r $tree_O $tree_A >.test-a &&
+    git-diff-tree -r -R $tree_A $tree_O >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree B A == diff-tree -R A B' \
+    'git-diff-tree $tree_B $tree_A >.test-a &&
+    git-diff-tree -R $tree_A $tree_B >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_expect_success \
+    'diff-tree -r B A == diff-tree -r -R A B' \
+    'git-diff-tree -r $tree_B $tree_A >.test-a &&
+    git-diff-tree -r -R $tree_A $tree_B >.test-b &&
+    cmp -s .test-a .test-b'
+
+test_done
diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh
new file mode 100755 (executable)
index 0000000..2751970
--- /dev/null
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-index --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  So we say you
+# copy-and-edit one, and rename-and-edit the other.  We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+diff --git a/COPYING b/COPYING.2
+rename from COPYING
+rename to COPYING.2
+--- a/COPYING
++++ b/COPYING.2
+@@ -2 +2 @@
+- Note that the only valid version of the GPL as far as this project
++ Note that the only valid version of the G.P.L as far as this project
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
+@@ -12 +12 @@
+-      This file is licensed under the GPL v2, or a later version
++      This file is licensed under the G.P.L v2, or a later version
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#1)' \
+    'compare_diff_patch current expected'
+
+test_expect_success \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  So we say you
+# edited one, and copy-and-edit the other.  We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -C -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING
+--- a/COPYING
++++ b/COPYING
+@@ -2 +2 @@
+- Note that the only valid version of the GPL as far as this project
++ Note that the only valid version of the G.P.L as far as this project
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
+@@ -12 +12 @@
+-      This file is licensed under the GPL v2, or a later version
++      This file is licensed under the G.P.L v2, or a later version
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#2)' \
+    'compare_diff_patch current expected'
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-index --add --remove COPYING COPYING.1'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# but COPYING is not edited.  We say you copy-and-edit COPYING.1; this
+# is only possible because -C mode now reports the unmodified file to
+# the diff-core.  Unchanged rezrov, although being fed to
+# git-diff-index as well, should not be mentioned.
+
+GIT_DIFF_OPTS=--unified=0 \
+    git-diff-index -C --find-copies-harder -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#3)' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh
new file mode 100755 (executable)
index 0000000..a23aaa0
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection tests.
+
+The rename detection logic should be able to detect pure rename or
+copy of symbolic links, but should not produce rename/copy followed
+by an edit for them.
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+test_expect_success \
+    'prepare reference tree' \
+    'echo xyzzy | tr -d '\\\\'012 >yomin &&
+     ln -s xyzzy frotz &&
+    git-update-index --add frotz yomin &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'mv frotz rezrov &&
+     rm -f yomin &&
+     ln -s xyzzy nitfol &&
+     ln -s xzzzy bozbar &&
+    git-update-index --add --remove frotz rezrov nitfol bozbar yomin'
+
+# tree has frotz pointing at xyzzy, and yomin that contains xyzzy to
+# confuse things.  work tree has rezrov (xyzzy) nitfol (xyzzy) and
+# bozbar (xzzzy).
+# rezrov and nitfol are rename/copy of frotz and bozbar should be
+# a new creation.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+cat >expected <<\EOF
+diff --git a/bozbar b/bozbar
+new file mode 120000
+--- /dev/null
++++ b/bozbar
+@@ -0,0 +1 @@
++xzzzy
+\ No newline at end of file
+diff --git a/frotz b/nitfol
+similarity index 100%
+copy from frotz
+copy to nitfol
+diff --git a/frotz b/rezrov
+similarity index 100%
+rename from frotz
+rename to rezrov
+diff --git a/yomin b/yomin
+deleted file mode 100644
+--- a/yomin
++++ /dev/null
+@@ -1 +0,0 @@
+-xyzzy
+\ No newline at end of file
+EOF
+
+test_expect_success \
+    'validate diff output' \
+    'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh
new file mode 100755 (executable)
index 0000000..684fd23
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-index --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-index -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234 COPYING COPYING.2
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#1)' \
+    'compare_diff_raw current expected'
+
+################################################################
+
+test_expect_success \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-index -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M     COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#2)' \
+    'compare_diff_raw current expected'
+
+################################################################
+
+# tree has COPYING and rezrov.  work tree has the same COPYING and
+# copy-edited COPYING.1, and unchanged rezrov.  We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-index --add --remove COPYING COPYING.1'
+
+git-diff-index -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#3)' \
+    'compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
new file mode 100755 (executable)
index 0000000..8ad69d1
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test mode change diffs.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'echo frotz >rezrov &&
+     git-update-index --add rezrov &&
+     tree=`git-write-tree` &&
+     echo $tree'
+
+if [ "$(git repo-config --get core.filemode)" = false ]
+then
+       say 'filemode disabled on the filesystem, using update-index --chmod=+x'
+       test_expect_success \
+           'git-update-index --chmod=+x' \
+           'git-update-index rezrov &&
+            git-update-index --chmod=+x rezrov &&
+            git-diff-index $tree >current'
+else
+       test_expect_success \
+           'chmod' \
+           'chmod +x rezrov &&
+            git-update-index rezrov &&
+            git-diff-index $tree >current'
+fi
+
+_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"
+sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
+echo ":100644 100755 X X M     rezrov" >expected
+
+test_expect_success \
+    'verify' \
+    'diff -u expected check'
+
+test_done
+
diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh
new file mode 100755 (executable)
index 0000000..bb6ba69
--- /dev/null
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Rename interaction with pathspec.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'mkdir path0 path1 &&
+     cp ../../COPYING path0/COPYING &&
+     git-update-index --add path0/COPYING &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'cp path0/COPYING path1/COPYING &&
+     git-update-index --add --remove path0/COPYING path1/COPYING'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# Comparing the full tree with cache should tell us so.
+
+git-diff-index -C --find-copies-harder $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100  path0/COPYING   path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#1)' \
+    'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# However when we say we care only about path1, we should just see
+# path1/COPYING suddenly appearing from nowhere, not detected as
+# a copy from path0/COPYING.
+
+git-diff-index -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#2)' \
+    'compare_diff_raw current expected'
+
+test_expect_success \
+    'tweak work tree' \
+    'rm -f path0/COPYING &&
+     git-update-index --remove path0/COPYING'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING.  Showing the full tree with cache should tell us about
+# the rename.
+
+git-diff-index -C $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  path0/COPYING   path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#3)' \
+    'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING.  In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING.  When we say we care only about path1, we should just
+# see path1/COPYING appearing from nowhere.
+
+git-diff-index -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
+EOF
+
+test_expect_success \
+    'validate the result (#4)' \
+    'compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
new file mode 100755 (executable)
index 0000000..263ac1e
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Break and then rename
+
+We have two very different files, file0 and file1, registered in a tree.
+
+We update file1 so drastically that it is more similar to file0, and
+then remove file0.  With -B, changes to file1 should be broken into
+separate delete and create, resulting in removal of file0, removal of
+original file1 and creation of completely rewritten file1.
+
+Further, with -B and -M together, these three modifications should
+turn into rename-edit of file0 into file1.
+
+Starting from the same two files in the tree, we swap file0 and file1.
+With -B, this should be detected as two complete rewrites, resulting in
+four changes in total.
+
+Further, with -B and -M together, these should turn into two renames.
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    setup \
+    'cat ../../README >file0 &&
+     cat ../../COPYING >file1 &&
+    git-update-index --add file0 file1 &&
+    tree=$(git-write-tree) &&
+    echo "$tree"'
+
+test_expect_success \
+    'change file1 with copy-edit of file0 and remove file0' \
+    'sed -e "s/git/GIT/" file0 >file1 &&
+     rm -f file0 &&
+    git-update-index --remove file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-index -B --cached "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 11e331465a89c394dc25c780de230043750c1ec8 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#1)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B and -M' \
+    'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100  file0   file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#2)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'swap file0 and file1' \
+    'rm -f file0 file1 &&
+     git-read-tree -m $tree &&
+     git-checkout-index -f -u -a &&
+     mv file0 tmp &&
+     mv file1 file0 &&
+     mv tmp file1 &&
+     git-update-index file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100  file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#3)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B and -M' \
+    'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  file1   file0
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R100  file0   file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#4)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'make file0 into something completely different' \
+    'rm -f file0 &&
+     ln -s frotz file0 &&
+     git-update-index file0 file1'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B (#5)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-index -B -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#6)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -M' \
+    'git-diff-index -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M     file1
+EOF
+
+test_expect_success \
+    'validate result of -M (#7)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'file1 edited to look like file0 and file0 rename-edited to file2' \
+    'rm -f file0 file1 &&
+     git-read-tree -m $tree &&
+     git-checkout-index -f -u -a &&
+     sed -e "s/git/GIT/" file0 >file1 &&
+     sed -e "s/git/GET/" file0 >file2 &&
+     rm -f file0
+     git-update-index --add --remove file0 file1 file2'
+
+test_expect_success \
+    'run diff with -B' \
+    'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 08bb2fb671deff4c03a4d4a0a1315dff98d5732c M100  file1
+:000000 100644 0000000000000000000000000000000000000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 A     file2
+EOF
+
+test_expect_success \
+    'validate result of -B (#8)' \
+    'compare_diff_raw expected current'
+
+test_expect_success \
+    'run diff with -B -M' \
+    'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095  file0   file1
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 59f832e5c8b3f7e486be15ad0cd3e95ba9af8998 R095  file0   file2
+EOF
+
+test_expect_success \
+    'validate result of -B -M (#9)' \
+    'compare_diff_raw expected current'
+
+test_done
diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh
new file mode 100755 (executable)
index 0000000..2f2f8b1
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw -z.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    'prepare reference tree' \
+    'cat ../../COPYING >COPYING &&
+     echo frotz >rezrov &&
+    git-update-index --add COPYING rezrov &&
+    tree=$(git-write-tree) &&
+    echo $tree'
+
+test_expect_success \
+    'prepare work tree' \
+    'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+    sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+    rm -f COPYING &&
+    git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-index -z -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234
+COPYING
+COPYING.2
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#1)' \
+    'compare_diff_raw_z current expected'
+
+################################################################
+
+test_expect_success \
+    'prepare work tree again' \
+    'mv COPYING.2 COPYING &&
+     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov.  We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-index -z -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
+COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#2)' \
+    'compare_diff_raw_z current expected'
+
+################################################################
+
+# tree has COPYING and rezrov.  work tree has the same COPYING and
+# copy-edited COPYING.1, and unchanged rezrov.  We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+    'prepare work tree once again' \
+    'cat ../../COPYING >COPYING &&
+     git-update-index --add --remove COPYING COPYING.1'
+
+git-diff-index -z -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+test_expect_success \
+    'validate output from rename/copy detection (#3)' \
+    'compare_diff_raw_z current expected'
+
+test_done
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
new file mode 100755 (executable)
index 0000000..9e1544d
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathspec restrictions
+
+Prepare:
+        file0
+        path1/file1
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+    setup \
+    'echo frotz >file0 &&
+     mkdir path1 &&
+     echo rezrov >path1/file1 &&
+     git-update-index --add file0 path1/file1 &&
+     tree=`git-write-tree` &&
+     echo "$tree" &&
+     echo nitfol >file0 &&
+     echo yomin >path1/file1 &&
+     git-update-index file0 path1/file1'
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+    'limit to path should show nothing' \
+    'git-diff-index --cached $tree -- path >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    'limit to path1 should show path1/file1' \
+    'git-diff-index --cached $tree -- path1 >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     path1/file1
+EOF
+test_expect_success \
+    'limit to path1/ should show path1/file1' \
+    'git-diff-index --cached $tree -- path1/ >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     file0
+EOF
+test_expect_success \
+    'limit to file0 should show file0' \
+    'git-diff-index --cached $tree -- file0 >current &&
+     compare_diff_raw current expected'
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+    'limit to file0/ should emit nothing.' \
+    'git-diff-index --cached $tree -- file0/ >current &&
+     compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
new file mode 100755 (executable)
index 0000000..379a831
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test diff of symlinks.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+cat > expected << EOF
+diff --git a/frotz b/frotz
+new file mode 120000
+index 0000000..7c465af
+--- /dev/null
++++ b/frotz
+@@ -0,0 +1 @@
++xyzzy
+\ No newline at end of file
+EOF
+
+test_expect_success \
+    'diff new symlink' \
+    'ln -s xyzzy frotz &&
+    git-update-index &&
+    tree=$(git-write-tree) &&
+    git-update-index --add frotz &&
+    GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree > current &&
+    compare_diff_patch current expected'
+
+test_expect_success \
+    'diff unchanged symlink' \
+    'tree=$(git-write-tree) &&
+    git-update-index frotz &&
+    test -z "$(git-diff-index --name-only $tree)"'
+
+cat > expected << EOF
+diff --git a/frotz b/frotz
+deleted file mode 120000
+index 7c465af..0000000
+--- a/frotz
++++ /dev/null
+@@ -1 +0,0 @@
+-xyzzy
+\ No newline at end of file
+EOF
+
+test_expect_success \
+    'diff removed symlink' \
+    'rm frotz &&
+    git-diff-index -M -p $tree > current &&
+    compare_diff_patch current expected'
+
+cat > expected << EOF
+diff --git a/frotz b/frotz
+EOF
+
+test_expect_success \
+    'diff identical, but newly created symlink' \
+    'sleep 3 &&
+    ln -s xyzzy frotz &&
+    git-diff-index -M -p $tree > current &&
+    compare_diff_patch current expected'
+
+cat > expected << EOF
+diff --git a/frotz b/frotz
+index 7c465af..df1db54 120000
+--- a/frotz
++++ b/frotz
+@@ -1 +1 @@
+-xyzzy
+\ No newline at end of file
++yxyyz
+\ No newline at end of file
+EOF
+
+test_expect_success \
+    'diff different symlink' \
+    'rm frotz &&
+    ln -s yxyyz frotz &&
+    git-diff-index -M -p $tree > current &&
+    compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
new file mode 100755 (executable)
index 0000000..323606c
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Binary diff and apply
+'
+
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' \
+       'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
+        git-update-index --add a b c d &&
+        echo git >a &&
+        cat ../test4012.png >b &&
+        echo git >c &&
+        cat b b >d'
+
+cat > expected <<\EOF
+ a |    2 +-
+ b |  Bin
+ c |    2 +-
+ d |  Bin
+ 4 files changed, 2 insertions(+), 2 deletions(-)
+EOF
+test_expect_success 'diff without --binary' \
+       'git-diff | git-apply --stat --summary >current &&
+        cmp current expected'
+
+test_expect_success 'diff with --binary' \
+       'git-diff --binary | git-apply --stat --summary >current &&
+        cmp current expected'
+
+# apply needs to be able to skip the binary material correctly
+# in order to report the line number of a corrupt patch.
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'initial commit' 'git-commit -a -m initial'
+
+# Try removal (b), modification (d), and creation (e).
+test_expect_success 'diff-index with --binary' \
+       'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
+        git-update-index --add --remove a b c d e &&
+        tree0=`git-write-tree` &&
+        git-diff --cached --binary >current &&
+        git-apply --stat --summary current'
+
+test_expect_success 'apply binary patch' \
+       'git-reset --hard &&
+        git-apply --binary --index <current &&
+        tree1=`git-write-tree` &&
+        test "$tree1" = "$tree0"'
+
+test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
new file mode 100755 (executable)
index 0000000..6579f06
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply --stat --summary test.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+    'rename' \
+    'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
+    diff -u ../t4100/t-apply-1.expect current'
+
+test_expect_success \
+    'copy' \
+    'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
+    diff -u ../t4100/t-apply-2.expect current'
+
+test_expect_success \
+    'rewrite' \
+    'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
+    diff -u ../t4100/t-apply-3.expect current'
+
+test_expect_success \
+    'mode' \
+    'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
+    diff -u ../t4100/t-apply-4.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
+    diff -u ../t4100/t-apply-5.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
+    diff -u ../t4100/t-apply-6.expect current'
+
+test_expect_success \
+    'non git' \
+    'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
+    diff -u ../t4100/t-apply-7.expect current'
+
+test_done
+
diff --git a/t/t4100/t-apply-1.expect b/t/t4100/t-apply-1.expect
new file mode 100644 (file)
index 0000000..540e64d
--- /dev/null
@@ -0,0 +1,11 @@
+ Documentation/git-ssh-pull.txt |   12 ++++++------
+ Documentation/git-ssh-push.txt |   10 +++++-----
+ Documentation/git.txt          |    6 +++---
+ Makefile                       |    6 +++---
+ ssh-pull.c                     |    4 ++--
+ ssh-push.c                     |   14 +++++++-------
+ 6 files changed, 26 insertions(+), 26 deletions(-)
+ rename Documentation/{git-rpull.txt => git-ssh-pull.txt} (90%)
+ rename Documentation/{git-rpush.txt => git-ssh-push.txt} (71%)
+ rename rpull.c => ssh-pull.c (97%)
+ rename rpush.c => ssh-push.c (93%)
diff --git a/t/t4100/t-apply-1.patch b/t/t4100/t-apply-1.patch
new file mode 100644 (file)
index 0000000..de58751
--- /dev/null
@@ -0,0 +1,194 @@
+418aaf847a8b3ffffb4f777a2dd5262ca5ce0ef7 (from dc93841715dfa9a9cdda6f2c4a25eec831ea7aa0)
+diff --git a/Documentation/git-rpull.txt b/Documentation/git-ssh-pull.txt
+similarity index 90%
+rename from Documentation/git-rpull.txt
+rename to Documentation/git-ssh-pull.txt
+--- a/Documentation/git-rpull.txt
++++ b/Documentation/git-ssh-pull.txt
+@@ -1,21 +1,21 @@
+-git-rpull(1)
+-============
++git-ssh-pull(1)
++===============
+ v0.1, May 2005
+ NAME
+ ----
+-git-rpull - Pulls from a remote repository over ssh connection
++git-ssh-pull - Pulls from a remote repository over ssh connection
+ SYNOPSIS
+ --------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+ DESCRIPTION
+ -----------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
+ OPTIONS
+ -------
+diff --git a/Documentation/git-rpush.txt b/Documentation/git-ssh-push.txt
+similarity index 71%
+rename from Documentation/git-rpush.txt
+rename to Documentation/git-ssh-push.txt
+--- a/Documentation/git-rpush.txt
++++ b/Documentation/git-ssh-push.txt
+@@ -1,19 +1,19 @@
+-git-rpush(1)
+-============
++git-ssh-push(1)
++===============
+ v0.1, May 2005
+ NAME
+ ----
+-git-rpush - Helper "server-side" program used by git-rpull
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
+ SYNOPSIS
+ --------
+-'git-rpush'
++'git-ssh-push'
+ DESCRIPTION
+ -----------
+-Helper "server-side" program used by git-rpull.
++Helper "server-side" program used by git-ssh-pull.
+ Author
+diff --git a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+       An example script to create a tag object signed with GPG
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+       Generates patch format output for git-diff-*
+-link:git-rpush.html[git-rpush]::
+-      Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++      Helper "server-side" program used by git-ssh-pull
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+       git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+       git-check-files git-ls-tree git-merge-base git-merge-cache \
+       git-unpack-file git-export git-diff-cache git-convert-cache \
+-      git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++      git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+       git-diff-helper git-tar-tree git-local-pull git-write-blob \
+       git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff --git a/rpull.c b/ssh-pull.c
+similarity index 97%
+rename from rpull.c
+rename to ssh-pull.c
+--- a/rpull.c
++++ b/ssh-pull.c
+@@ -64,13 +64,13 @@ int main(int argc, char **argv)
+               arg++;
+       }
+       if (argc < arg + 2) {
+-              usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++              usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+               return 1;
+       }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
+               return 1;
+       if (get_version())
+diff --git a/rpush.c b/ssh-push.c
+similarity index 93%
+rename from rpush.c
+rename to ssh-push.c
+--- a/rpush.c
++++ b/ssh-push.c
+@@ -16,7 +16,7 @@ int serve_object(int fd_in, int fd_out) 
+       do {
+               size = read(fd_in, sha1 + posn, 20 - posn);
+               if (size < 0) {
+-                      perror("git-rpush: read ");
++                      perror("git-ssh-push: read ");
+                       return -1;
+               }
+               if (!size)
+@@ -30,7 +30,7 @@ int serve_object(int fd_in, int fd_out) 
+       buf = map_sha1_file(sha1, &objsize);
+       
+       if (!buf) {
+-              fprintf(stderr, "git-rpush: could not find %s\n", 
++              fprintf(stderr, "git-ssh-push: could not find %s\n", 
+                       sha1_to_hex(sha1));
+               remote = -1;
+       }
+@@ -45,9 +45,9 @@ int serve_object(int fd_in, int fd_out) 
+               size = write(fd_out, buf + posn, objsize - posn);
+               if (size <= 0) {
+                       if (!size) {
+-                              fprintf(stderr, "git-rpush: write closed");
++                              fprintf(stderr, "git-ssh-push: write closed");
+                       } else {
+-                              perror("git-rpush: write ");
++                              perror("git-ssh-push: write ");
+                       }
+                       return -1;
+               }
+@@ -71,7 +71,7 @@ void service(int fd_in, int fd_out) {
+               retval = read(fd_in, &type, 1);
+               if (retval < 1) {
+                       if (retval < 0)
+-                              perror("rpush: read ");
++                              perror("git-ssh-push: read ");
+                       return;
+               }
+               if (type == 'v' && serve_version(fd_in, fd_out))
+@@ -91,12 +91,12 @@ int main(int argc, char **argv)
+                 arg++;
+         }
+         if (argc < arg + 2) {
+-              usage("git-rpush [-c] [-t] [-a] commit-id url");
++              usage("git-ssh-push [-c] [-t] [-a] commit-id url");
+                 return 1;
+         }
+       commit_id = argv[arg];
+       url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
+               return 1;
+       service(fd_in, fd_out);
diff --git a/t/t4100/t-apply-2.expect b/t/t4100/t-apply-2.expect
new file mode 100644 (file)
index 0000000..d1e6459
--- /dev/null
@@ -0,0 +1,5 @@
+ Makefile         |    2 +-
+ git-fetch-script |    5 -----
+ git-pull-script  |   34 +---------------------------------
+ 3 files changed, 2 insertions(+), 39 deletions(-)
+ copy git-pull-script => git-fetch-script (87%)
diff --git a/t/t4100/t-apply-2.patch b/t/t4100/t-apply-2.patch
new file mode 100644 (file)
index 0000000..cfdc808
--- /dev/null
@@ -0,0 +1,72 @@
+7ef76925d9c19ef74874e1735e2436e56d0c4897 (from 6b14d7faf0bad026a81a27bac07b47691f621b8f)
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+       git-pull-script git-tag-script git-resolve-script git-whatchanged \
+-      git-deltafy-script
++      git-deltafy-script git-fetch-script
+ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+       git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff --git a/git-pull-script b/git-fetch-script
+similarity index 87%
+copy from git-pull-script
+copy to git-fetch-script
+--- a/git-pull-script
++++ b/git-fetch-script
+@@ -39,8 +39,3 @@ download_one "$merge_repo/$merge_name" "
+ echo "Getting object database"
+ download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+-
+-git-resolve-script \
+-      "$(cat "$GIT_DIR"/HEAD)" \
+-      "$(cat "$GIT_DIR"/MERGE_HEAD)" \
+-      "$merge_repo"
+diff --git a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+-download_one () {
+-      # remote_path="$1" local_file="$2"
+-      case "$1" in
+-      http://*)
+-              wget -q -O "$2" "$1" ;;
+-      /*)
+-              test -f "$1" && cat >"$2" "$1" ;;
+-      *)
+-              rsync -L "$1" "$2" ;;
+-      esac
+-}
+-
+-download_objects () {
+-      # remote_repo="$1" head_sha1="$2"
+-      case "$1" in
+-      http://*)
+-              git-http-pull -a "$2" "$1/"
+-              ;;
+-      /*)
+-              git-local-pull -l -a "$2" "$1/"
+-              ;;
+-      *)
+-              rsync -avz --ignore-existing \
+-                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+-              ;;
+-      esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+ git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-3.expect b/t/t4100/t-apply-3.expect
new file mode 100644 (file)
index 0000000..912a552
--- /dev/null
@@ -0,0 +1,7 @@
+ Documentation/git-ls-tree.txt |   20 +-
+ ls-tree.c                     |  459 ++++++++++++++++++++++-------------------
+ t/t3100-ls-tree-restrict.sh   |    3 
+ tree.c                        |    2 
+ tree.h                        |    1 
+ 5 files changed, 262 insertions(+), 223 deletions(-)
+ rewrite ls-tree.c (82%)
diff --git a/t/t4100/t-apply-3.patch b/t/t4100/t-apply-3.patch
new file mode 100644 (file)
index 0000000..90cdbaa
--- /dev/null
@@ -0,0 +1,567 @@
+6af1f0192ff8740fe77db7cf02c739ccfbdf119c (from 2bc2564145835996734d6ed5d1880f85b17233d6)
+diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+ OPTIONS
+ -------
+ <tree-ish>::
+       Id of a tree.
++-d::
++      show only the named tree entry itself, not its children
++
+ -r::
+       recurse into sub-trees
+@@ -28,18 +31,19 @@ OPTIONS
+       \0 line termination on output
+ paths::
+-      Optionally, restrict the output of git-ls-tree to specific
+-      paths. Directories will only list their tree blob ids.
+-      Implies -r.
++      When paths are given, shows them.  Otherwise implicitly
++      uses the root level of the tree as the sole path argument.
++
+ Output Format
+ -------------
+-        <mode>\t      <type>\t        <object>\t      <file>
++        <mode> SP <type> SP <object> TAB <file>
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+ Documentation
+ --------------
+diff --git a/ls-tree.c b/ls-tree.c
+dissimilarity index 82%
+--- ls-tree.c
++++ ls-tree.c
+@@ -1,212 +1,247 @@
+-/*
+- * GIT - The information manager from hell
+- *
+- * Copyright (C) Linus Torvalds, 2005
+- */
+-#include "cache.h"
+-
+-static int line_termination = '\n';
+-static int recursive = 0;
+-
+-struct path_prefix {
+-      struct path_prefix *prev;
+-      const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)       
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+-      int len = 0;
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      len = string_path_prefix(buff,blen,prefix->prev);
+-                      buff += len;
+-                      blen -= len;
+-                      if (blen > 0) {
+-                              *buff = '/';
+-                              len++;
+-                              buff++;
+-                              blen--;
+-                      }
+-              }
+-              strncpy(buff,prefix->name,blen);
+-              return len + strlen(prefix->name);
+-      }
+-
+-      return 0;
+-}
+-
+-static void print_path_prefix(struct path_prefix *prefix)
+-{
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      print_path_prefix(prefix->prev);
+-                      putchar('/');
+-              }
+-              fputs(prefix->name, stdout);
+-      }
+-}
+-
+-/*
+- * return:
+- *    -1 if prefix is *not* a subset of path
+- *     0 if prefix == path
+- *     1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+-      char buff[PATH_MAX];
+-      int len,slen;
+-
+-      if (prefix == NULL)
+-              return 1;
+-
+-      len = string_path_prefix(buff, sizeof buff, prefix);
+-      slen = strlen(path);
+-
+-      if (slen < len)
+-              return -1;
+-
+-      if (strncmp(path,buff,len) == 0) {
+-              if (slen == len)
+-                      return 0;
+-              else
+-                      return 1;
+-      }
+-
+-      return -1;
+-}     
+-
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+-                         const char *type,
+-                         unsigned long size,
+-                         struct path_prefix *prefix,
+-                         char **match, int matches)
+-{
+-      struct path_prefix this_prefix;
+-      this_prefix.prev = prefix;
+-
+-      if (strcmp(type, "tree"))
+-              die("expected a 'tree' node");
+-
+-      if (matches)
+-              recursive = 1;
+-
+-      while (size) {
+-              int namelen = strlen(buffer)+1;
+-              void *eltbuf = NULL;
+-              char elttype[20];
+-              unsigned long eltsize;
+-              unsigned char *sha1 = buffer + namelen;
+-              char *path = strchr(buffer, ' ') + 1;
+-              unsigned int mode;
+-              const char *matched = NULL;
+-              int mtype = -1;
+-              int mindex;
+-
+-              if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+-                      die("corrupt 'tree' file");
+-              buffer = sha1 + 20;
+-              size -= namelen + 20;
+-
+-              this_prefix.name = path;
+-              for ( mindex = 0; mindex < matches; mindex++) {
+-                      mtype = pathcmp(match[mindex],&this_prefix);
+-                      if (mtype >= 0) {
+-                              matched = match[mindex];
+-                              break;
+-                      }
+-              }
+-
+-              /*
+-               * If we're not matching, or if this is an exact match,
+-               * print out the info
+-               */
+-              if (!matches || (matched != NULL && mtype == 0)) {
+-                      printf("%06o %s %s\t", mode,
+-                             S_ISDIR(mode) ? "tree" : "blob",
+-                             sha1_to_hex(sha1));
+-                      print_path_prefix(&this_prefix);
+-                      putchar(line_termination);
+-              }
+-
+-              if (! recursive || ! S_ISDIR(mode))
+-                      continue;
+-
+-              if (matches && ! matched)
+-                      continue;
+-
+-              if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+-                      error("cannot read %s", sha1_to_hex(sha1));
+-                      continue;
+-              }
+-
+-              /* If this is an exact directory match, we may have
+-               * directory files following this path. Match on them.
+-               * Otherwise, we're at a pach subcomponent, and we need
+-               * to try to match again.
+-               */
+-              if (mtype == 0)
+-                      mindex++;
+-
+-              list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+-              free(eltbuf);
+-      }
+-}
+-
+-static int qcmp(const void *a, const void *b)
+-{
+-      return strcmp(*(char **)a, *(char **)b);
+-}
+-
+-static int list(unsigned char *sha1,char **path)
+-{
+-      void *buffer;
+-      unsigned long size;
+-      int npaths;
+-
+-      for (npaths = 0; path[npaths] != NULL; npaths++)
+-              ;
+-
+-      qsort(path,npaths,sizeof(char *),qcmp);
+-
+-      buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+-      if (!buffer)
+-              die("unable to read sha1 file");
+-      list_recursive(buffer, "tree", size, NULL, path, npaths);
+-      free(buffer);
+-      return 0;
+-}
+-
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
+-
+-int main(int argc, char **argv)
+-{
+-      unsigned char sha1[20];
+-
+-      while (1 < argc && argv[1][0] == '-') {
+-              switch (argv[1][1]) {
+-              case 'z':
+-                      line_termination = 0;
+-                      break;
+-              case 'r':
+-                      recursive = 1;
+-                      break;
+-              default:
+-                      usage(ls_tree_usage);
+-              }
+-              argc--; argv++;
+-      }
+-
+-      if (argc < 2)
+-              usage(ls_tree_usage);
+-      if (get_sha1(argv[1], sha1) < 0)
+-              usage(ls_tree_usage);
+-      if (list(sha1, &argv[2]) < 0)
+-              die("list failed");
+-      return 0;
+-}
++/*
++ * GIT - The information manager from hell
++ *
++ * Copyright (C) Linus Torvalds, 2005
++ */
++#include "cache.h"
++#include "blob.h"
++#include "tree.h"
++
++static int line_termination = '\n';
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
++
++static struct tree_entry_list root_entry;
++
++static void prepare_root(unsigned char *sha1)
++{
++      unsigned char rsha[20];
++      unsigned long size;
++      void *buf;
++      struct tree *root_tree;
++
++      buf = read_object_with_reference(sha1, "tree", &size, rsha);
++      free(buf);
++      if (!buf)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      root_tree = lookup_tree(rsha);
++      if (!root_tree)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      /* Prepare a fake entry */
++      root_entry.directory = 1;
++      root_entry.executable = root_entry.symlink = 0;
++      root_entry.mode = S_IFDIR;
++      root_entry.name = "";
++      root_entry.item.tree = root_tree;
++      root_entry.parent = NULL;
++}
++
++static int prepare_children(struct tree_entry_list *elem)
++{
++      if (!elem->directory)
++              return -1;
++      if (!elem->item.tree->object.parsed) {
++              struct tree_entry_list *e;
++              if (parse_tree(elem->item.tree))
++                      return -1;
++              /* Set up the parent link */
++              for (e = elem->item.tree->entries; e; e = e->next)
++                      e->parent = elem;
++      }
++      return 0;
++}
++
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++                                          const char *path,
++                                          const char *path_end)
++{
++      const char *ep;
++      int len;
++
++      while (path < path_end) {
++              if (prepare_children(elem))
++                      return NULL;
++
++              /* In elem->tree->entries, find the one that has name
++               * that matches what is between path and ep.
++               */
++              elem = elem->item.tree->entries;
++
++              ep = strchr(path, '/');
++              if (!ep || path_end <= ep)
++                      ep = path_end;
++              len = ep - path;
++
++              while (elem) {
++                      if ((strlen(elem->name) == len) &&
++                          !strncmp(elem->name, path, len))
++                              break;
++                      elem = elem->next;
++              }
++              if (path_end <= ep || !elem)
++                      return elem;
++              while (*ep == '/' && ep < path_end)
++                      ep++;
++              path = ep;
++      }
++      return NULL;
++}
++
++static struct tree_entry_list *find_entry(const char *path,
++                                        const char *path_end)
++{
++      /* Find tree element, descending from root, that
++       * corresponds to the named path, lazily expanding
++       * the tree if possible.
++       */
++      if (path == path_end) {
++              /* Special.  This is the root level */
++              return &root_entry;
++      }
++      return find_entry_0(&root_entry, path, path_end);
++}
++
++static void show_entry_name(struct tree_entry_list *e)
++{
++      /* This is yucky.  The root level is there for
++       * our convenience but we really want to do a
++       * forest.
++       */
++      if (e->parent && e->parent != &root_entry) {
++              show_entry_name(e->parent);
++              putchar('/');
++      }
++      printf("%s", e->name);
++}
++
++static const char *entry_type(struct tree_entry_list *e)
++{
++      return (e->directory ? "tree" : "blob");
++}
++
++static const char *entry_hex(struct tree_entry_list *e)
++{
++      return sha1_to_hex(e->directory
++                         ? e->item.tree->object.sha1
++                         : e->item.blob->object.sha1);
++}
++
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
++
++static int show_children(struct tree_entry_list *e, int level)
++{
++      if (prepare_children(e))
++              die("internal error: ls-tree show_children called with non tree");
++      e = e->item.tree->entries;
++      while (e) {
++              show_entry(e, level);
++              e = e->next;
++      }
++      return 0;
++}
++
++static int show_entry(struct tree_entry_list *e, int level)
++{
++      int err = 0; 
++
++      if (e != &root_entry) {
++              printf("%06o %s %s      ", e->mode, entry_type(e),
++                     entry_hex(e));
++              show_entry_name(e);
++              putchar(line_termination);
++      }
++
++      if (e->directory) {
++              /* If this is a directory, we have the following cases:
++               * (1) This is the top-level request (explicit path from the
++               *     command line, or "root" if there is no command line).
++               *  a. Without any flag.  We show direct children.  We do not 
++               *     recurse into them.
++               *  b. With -r.  We do recurse into children.
++               *  c. With -d.  We do not recurse into children.
++               * (2) We came here because our caller is either (1-a) or
++               *     (1-b).
++               *  a. Without any flag.  We do not show our children (which
++               *     are grandchildren for the original request).
++               *  b. With -r.  We continue to recurse into our children.
++               *  c. With -d.  We should not have come here to begin with.
++               */
++              if (level == 0 && !(ls_options & LS_TREE_ONLY))
++                      /* case (1)-a and (1)-b */
++                      err = err | show_children(e, level+1);
++              else if (level && ls_options & LS_RECURSIVE)
++                      /* case (2)-b */
++                      err = err | show_children(e, level+1);
++      }
++      return err;
++}
++
++static int list_one(const char *path, const char *path_end)
++{
++      int err = 0;
++      struct tree_entry_list *e = find_entry(path, path_end);
++      if (!e) {
++              /* traditionally ls-tree does not complain about
++               * missing path.  We may change this later to match
++               * what "/bin/ls -a" does, which is to complain.
++               */
++              return err;
++      }
++      err = err | show_entry(e, 0);
++      return err;
++}
++
++static int list(char **path)
++{
++      int i;
++      int err = 0;
++      for (i = 0; path[i]; i++) {
++              int len = strlen(path[i]);
++              while (0 <= len && path[i][len] == '/')
++                      len--;
++              err = err | list_one(path[i], path[i] + len);
++      }
++      return err;
++}
++
++static const char *ls_tree_usage =
++      "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
++
++int main(int argc, char **argv)
++{
++      static char *path0[] = { "", NULL };
++      char **path;
++      unsigned char sha1[20];
++
++      while (1 < argc && argv[1][0] == '-') {
++              switch (argv[1][1]) {
++              case 'z':
++                      line_termination = 0;
++                      break;
++              case 'r':
++                      ls_options |= LS_RECURSIVE;
++                      break;
++              case 'd':
++                      ls_options |= LS_TREE_ONLY;
++                      break;
++              default:
++                      usage(ls_tree_usage);
++              }
++              argc--; argv++;
++      }
++
++      if (argc < 2)
++              usage(ls_tree_usage);
++      if (get_sha1(argv[1], sha1) < 0)
++              usage(ls_tree_usage);
++
++      path = (argc == 2) ? path0 : (argv + 2);
++      prepare_root(sha1);
++      if (list(path) < 0)
++              die("list failed");
++      return 0;
++}
+diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+     'ls-tree filtered' \
+     'git-ls-tree $tree path1 path0 >current &&
+      cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+      test_output'
+@@ -85,7 +85,6 @@ test_expect_success \
+      cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff --git a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+               }
+               if (obj)
+                       add_ref(&item->object, obj);
+-
++              entry->parent = NULL; /* needs to be filled by the user */
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+diff --git a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+               struct tree *tree;
+               struct blob *blob;
+       } item;
++      struct tree_entry_list *parent;
+ };
+ struct tree {
diff --git a/t/t4100/t-apply-4.expect b/t/t4100/t-apply-4.expect
new file mode 100644 (file)
index 0000000..1ec028b
--- /dev/null
@@ -0,0 +1,5 @@
+ t/t0000-basic.sh |    0 
+ t/test-lib.sh    |    0 
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+ mode change 100644 => 100755 t/t0000-basic.sh
+ mode change 100644 => 100755 t/test-lib.sh
diff --git a/t/t4100/t-apply-4.patch b/t/t4100/t-apply-4.patch
new file mode 100644 (file)
index 0000000..4a56ab5
--- /dev/null
@@ -0,0 +1,7 @@
+ceede59ea90cebad52ba9c8263fef3fb6ef17593 (from 368f99d57e8ed17243f2e164431449d48bfca2fb)
+diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
+old mode 100644
+new mode 100755
+diff --git a/t/test-lib.sh b/t/test-lib.sh
+old mode 100644
+new mode 100755
diff --git a/t/t4100/t-apply-5.expect b/t/t4100/t-apply-5.expect
new file mode 100644 (file)
index 0000000..b387df1
--- /dev/null
@@ -0,0 +1,19 @@
+ Documentation/git-rpull.txt    |   50 -------------------
+ Documentation/git-rpush.txt    |   30 ------------
+ Documentation/git-ssh-pull.txt |   50 +++++++++++++++++++
+ Documentation/git-ssh-push.txt |   30 ++++++++++++
+ Documentation/git.txt          |    6 +-
+ Makefile                       |    6 +-
+ rpull.c                        |   83 --------------------------------
+ rpush.c                        |  104 ----------------------------------------
+ ssh-pull.c                     |   83 ++++++++++++++++++++++++++++++++
+ ssh-push.c                     |  104 ++++++++++++++++++++++++++++++++++++++++
+ 10 files changed, 273 insertions(+), 273 deletions(-)
+ delete Documentation/git-rpull.txt
+ delete Documentation/git-rpush.txt
+ create Documentation/git-ssh-pull.txt
+ create Documentation/git-ssh-push.txt
+ delete rpull.c
+ delete rpush.c
+ create ssh-pull.c
+ create ssh-push.c
diff --git a/t/t4100/t-apply-5.patch b/t/t4100/t-apply-5.patch
new file mode 100644 (file)
index 0000000..de11623
--- /dev/null
@@ -0,0 +1,612 @@
+diff a/Documentation/git-rpull.txt b/Documentation/git-rpull.txt
+--- a/Documentation/git-rpull.txt
++++ /dev/null
+@@ -1,50 +0,0 @@
+-git-rpull(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpull - Pulls from a remote repository over ssh connection
+-
+-
+-
+-SYNOPSIS
+---------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+-
+-DESCRIPTION
+------------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
+-
+-OPTIONS
+--------
+--c::
+-      Get the commit objects.
+--t::
+-      Get trees associated with the commit objects.
+--a::
+-      Get all the objects.
+--d::
+-      Do not check for delta base objects (use this option
+-      only when you know the remote repository is not
+-      deltified).
+---recover::
+-      Check dependency of deltified object more carefully than
+-      usual, to recover after earlier pull that was interrupted.
+--v::
+-      Report what is downloaded.
+-
+-
+-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 link:git.html[git] suite
+-
+diff a/Documentation/git-rpush.txt b/Documentation/git-rpush.txt
+--- a/Documentation/git-rpush.txt
++++ /dev/null
+@@ -1,30 +0,0 @@
+-git-rpush(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpush - Helper "server-side" program used by git-rpull
+-
+-
+-SYNOPSIS
+---------
+-'git-rpush'
+-
+-DESCRIPTION
+------------
+-Helper "server-side" program used by git-rpull.
+-
+-
+-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 link:git.html[git] suite
+-
+diff a/Documentation/git-ssh-pull.txt b/Documentation/git-ssh-pull.txt
+--- /dev/null
++++ b/Documentation/git-ssh-pull.txt
+@@ -0,0 +1,50 @@
++git-ssh-pull(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-pull - Pulls from a remote repository over ssh connection
++
++
++
++SYNOPSIS
++--------
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++
++DESCRIPTION
++-----------
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
++
++OPTIONS
++-------
++-c::
++      Get the commit objects.
++-t::
++      Get trees associated with the commit objects.
++-a::
++      Get all the objects.
++-d::
++      Do not check for delta base objects (use this option
++      only when you know the remote repository is not
++      deltified).
++--recover::
++      Check dependency of deltified object more carefully than
++      usual, to recover after earlier pull that was interrupted.
++-v::
++      Report what is downloaded.
++
++
++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 link:git.html[git] suite
++
+diff a/Documentation/git-ssh-push.txt b/Documentation/git-ssh-push.txt
+--- /dev/null
++++ b/Documentation/git-ssh-push.txt
+@@ -0,0 +1,30 @@
++git-ssh-push(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
++
++
++SYNOPSIS
++--------
++'git-ssh-push'
++
++DESCRIPTION
++-----------
++Helper "server-side" program used by git-ssh-pull.
++
++
++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 link:git.html[git] suite
++
+diff a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+       An example script to create a tag object signed with GPG
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+       Generates patch format output for git-diff-*
+-link:git-rpush.html[git-rpush]::
+-      Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++      Helper "server-side" program used by git-ssh-pull
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+       git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+       git-check-files git-ls-tree git-merge-base git-merge-cache \
+       git-unpack-file git-export git-diff-cache git-convert-cache \
+-      git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++      git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+       git-diff-helper git-tar-tree git-local-pull git-write-blob \
+       git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff a/rpull.c b/rpull.c
+--- a/rpull.c
++++ /dev/null
+@@ -1,83 +0,0 @@
+-#include "cache.h"
+-#include "commit.h"
+-#include "rsh.h"
+-#include "pull.h"
+-
+-static int fd_in;
+-static int fd_out;
+-
+-static unsigned char remote_version = 0;
+-static unsigned char local_version = 1;
+-
+-int fetch(unsigned char *sha1)
+-{
+-      int ret;
+-      signed char remote;
+-      char type = 'o';
+-      if (has_sha1_file(sha1))
+-              return 0;
+-      write(fd_out, &type, 1);
+-      write(fd_out, sha1, 20);
+-      if (read(fd_in, &remote, 1) < 1)
+-              return -1;
+-      if (remote < 0)
+-              return remote;
+-      ret = write_sha1_from_fd(sha1, fd_in);
+-      if (!ret)
+-              pull_say("got %s\n", sha1_to_hex(sha1));
+-      return ret;
+-}
+-
+-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) {
+-              return error("Couldn't read version from remote end");
+-      }
+-      return 0;
+-}
+-
+-int main(int argc, char **argv)
+-{
+-      char *commit_id;
+-      char *url;
+-      int arg = 1;
+-
+-      while (arg < argc && argv[arg][0] == '-') {
+-              if (argv[arg][1] == 't') {
+-                      get_tree = 1;
+-              } else if (argv[arg][1] == 'c') {
+-                      get_history = 1;
+-              } else if (argv[arg][1] == 'd') {
+-                      get_delta = 0;
+-              } else if (!strcmp(argv[arg], "--recover")) {
+-                      get_delta = 2;
+-              } else if (argv[arg][1] == 'a') {
+-                      get_all = 1;
+-                      get_tree = 1;
+-                      get_history = 1;
+-              } else if (argv[arg][1] == 'v') {
+-                      get_verbosely = 1;
+-              }
+-              arg++;
+-      }
+-      if (argc < arg + 2) {
+-              usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+-              return 1;
+-      }
+-      commit_id = argv[arg];
+-      url = argv[arg + 1];
+-
+-      if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
+-              return 1;
+-
+-      if (get_version())
+-              return 1;
+-
+-      if (pull(commit_id))
+-              return 1;
+-
+-      return 0;
+-}
+diff a/rpush.c b/rpush.c
+--- a/rpush.c
++++ /dev/null
+@@ -1,104 +0,0 @@
+-#include "cache.h"
+-#include "rsh.h"
+-#include <sys/socket.h>
+-#include <errno.h>
+-
+-unsigned char local_version = 1;
+-unsigned char remote_version = 0;
+-
+-int serve_object(int fd_in, int fd_out) {
+-      ssize_t size;
+-      int posn = 0;
+-      char sha1[20];
+-      unsigned long objsize;
+-      void *buf;
+-      signed char remote;
+-      do {
+-              size = read(fd_in, sha1 + posn, 20 - posn);
+-              if (size < 0) {
+-                      perror("git-rpush: read ");
+-                      return -1;
+-              }
+-              if (!size)
+-                      return -1;
+-              posn += size;
+-      } while (posn < 20);
+-      
+-      /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
+-      remote = 0;
+-      
+-      buf = map_sha1_file(sha1, &objsize);
+-      
+-      if (!buf) {
+-              fprintf(stderr, "git-rpush: could not find %s\n", 
+-                      sha1_to_hex(sha1));
+-              remote = -1;
+-      }
+-      
+-      write(fd_out, &remote, 1);
+-      
+-      if (remote < 0)
+-              return 0;
+-      
+-      posn = 0;
+-      do {
+-              size = write(fd_out, buf + posn, objsize - posn);
+-              if (size <= 0) {
+-                      if (!size) {
+-                              fprintf(stderr, "git-rpush: write closed");
+-                      } else {
+-                              perror("git-rpush: write ");
+-                      }
+-                      return -1;
+-              }
+-              posn += size;
+-      } while (posn < objsize);
+-      return 0;
+-}
+-
+-int serve_version(int fd_in, int fd_out)
+-{
+-      if (read(fd_in, &remote_version, 1) < 1)
+-              return -1;
+-      write(fd_out, &local_version, 1);
+-      return 0;
+-}
+-
+-void service(int fd_in, int fd_out) {
+-      char type;
+-      int retval;
+-      do {
+-              retval = read(fd_in, &type, 1);
+-              if (retval < 1) {
+-                      if (retval < 0)
+-                              perror("rpush: read ");
+-                      return;
+-              }
+-              if (type == 'v' && serve_version(fd_in, fd_out))
+-                      return;
+-              if (type == 'o' && serve_object(fd_in, fd_out))
+-                      return;
+-      } while (1);
+-}
+-
+-int main(int argc, char **argv)
+-{
+-      int arg = 1;
+-        char *commit_id;
+-        char *url;
+-      int fd_in, fd_out;
+-      while (arg < argc && argv[arg][0] == '-') {
+-                arg++;
+-        }
+-        if (argc < arg + 2) {
+-              usage("git-rpush [-c] [-t] [-a] commit-id url");
+-                return 1;
+-        }
+-      commit_id = argv[arg];
+-      url = argv[arg + 1];
+-      if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
+-              return 1;
+-
+-      service(fd_in, fd_out);
+-      return 0;
+-}
+diff a/ssh-pull.c b/ssh-pull.c
+--- /dev/null
++++ b/ssh-pull.c
+@@ -0,0 +1,83 @@
++#include "cache.h"
++#include "commit.h"
++#include "rsh.h"
++#include "pull.h"
++
++static int fd_in;
++static int fd_out;
++
++static unsigned char remote_version = 0;
++static unsigned char local_version = 1;
++
++int fetch(unsigned char *sha1)
++{
++      int ret;
++      signed char remote;
++      char type = 'o';
++      if (has_sha1_file(sha1))
++              return 0;
++      write(fd_out, &type, 1);
++      write(fd_out, sha1, 20);
++      if (read(fd_in, &remote, 1) < 1)
++              return -1;
++      if (remote < 0)
++              return remote;
++      ret = write_sha1_from_fd(sha1, fd_in);
++      if (!ret)
++              pull_say("got %s\n", sha1_to_hex(sha1));
++      return ret;
++}
++
++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) {
++              return error("Couldn't read version from remote end");
++      }
++      return 0;
++}
++
++int main(int argc, char **argv)
++{
++      char *commit_id;
++      char *url;
++      int arg = 1;
++
++      while (arg < argc && argv[arg][0] == '-') {
++              if (argv[arg][1] == 't') {
++                      get_tree = 1;
++              } else if (argv[arg][1] == 'c') {
++                      get_history = 1;
++              } else if (argv[arg][1] == 'd') {
++                      get_delta = 0;
++              } else if (!strcmp(argv[arg], "--recover")) {
++                      get_delta = 2;
++              } else if (argv[arg][1] == 'a') {
++                      get_all = 1;
++                      get_tree = 1;
++                      get_history = 1;
++              } else if (argv[arg][1] == 'v') {
++                      get_verbosely = 1;
++              }
++              arg++;
++      }
++      if (argc < arg + 2) {
++              usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++              return 1;
++      }
++      commit_id = argv[arg];
++      url = argv[arg + 1];
++
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
++              return 1;
++
++      if (get_version())
++              return 1;
++
++      if (pull(commit_id))
++              return 1;
++
++      return 0;
++}
+diff a/ssh-push.c b/ssh-push.c
+--- /dev/null
++++ b/ssh-push.c
+@@ -0,0 +1,104 @@
++#include "cache.h"
++#include "rsh.h"
++#include <sys/socket.h>
++#include <errno.h>
++
++unsigned char local_version = 1;
++unsigned char remote_version = 0;
++
++int serve_object(int fd_in, int fd_out) {
++      ssize_t size;
++      int posn = 0;
++      char sha1[20];
++      unsigned long objsize;
++      void *buf;
++      signed char remote;
++      do {
++              size = read(fd_in, sha1 + posn, 20 - posn);
++              if (size < 0) {
++                      perror("git-ssh-push: read ");
++                      return -1;
++              }
++              if (!size)
++                      return -1;
++              posn += size;
++      } while (posn < 20);
++      
++      /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
++      remote = 0;
++      
++      buf = map_sha1_file(sha1, &objsize);
++      
++      if (!buf) {
++              fprintf(stderr, "git-ssh-push: could not find %s\n", 
++                      sha1_to_hex(sha1));
++              remote = -1;
++      }
++      
++      write(fd_out, &remote, 1);
++      
++      if (remote < 0)
++              return 0;
++      
++      posn = 0;
++      do {
++              size = write(fd_out, buf + posn, objsize - posn);
++              if (size <= 0) {
++                      if (!size) {
++                              fprintf(stderr, "git-ssh-push: write closed");
++                      } else {
++                              perror("git-ssh-push: write ");
++                      }
++                      return -1;
++              }
++              posn += size;
++      } while (posn < objsize);
++      return 0;
++}
++
++int serve_version(int fd_in, int fd_out)
++{
++      if (read(fd_in, &remote_version, 1) < 1)
++              return -1;
++      write(fd_out, &local_version, 1);
++      return 0;
++}
++
++void service(int fd_in, int fd_out) {
++      char type;
++      int retval;
++      do {
++              retval = read(fd_in, &type, 1);
++              if (retval < 1) {
++                      if (retval < 0)
++                              perror("git-ssh-push: read ");
++                      return;
++              }
++              if (type == 'v' && serve_version(fd_in, fd_out))
++                      return;
++              if (type == 'o' && serve_object(fd_in, fd_out))
++                      return;
++      } while (1);
++}
++
++int main(int argc, char **argv)
++{
++      int arg = 1;
++        char *commit_id;
++        char *url;
++      int fd_in, fd_out;
++      while (arg < argc && argv[arg][0] == '-') {
++                arg++;
++        }
++        if (argc < arg + 2) {
++              usage("git-ssh-push [-c] [-t] [-a] commit-id url");
++                return 1;
++        }
++      commit_id = argv[arg];
++      url = argv[arg + 1];
++      if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
++              return 1;
++
++      service(fd_in, fd_out);
++      return 0;
++}
diff --git a/t/t4100/t-apply-6.expect b/t/t4100/t-apply-6.expect
new file mode 100644 (file)
index 0000000..1c343d4
--- /dev/null
@@ -0,0 +1,5 @@
+ Makefile         |    2 +-
+ git-fetch-script |   41 +++++++++++++++++++++++++++++++++++++++++
+ git-pull-script  |   34 +---------------------------------
+ 3 files changed, 43 insertions(+), 34 deletions(-)
+ create git-fetch-script
diff --git a/t/t4100/t-apply-6.patch b/t/t4100/t-apply-6.patch
new file mode 100644 (file)
index 0000000..d975363
--- /dev/null
@@ -0,0 +1,101 @@
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+       git-pull-script git-tag-script git-resolve-script git-whatchanged \
+-      git-deltafy-script
++      git-deltafy-script git-fetch-script
+ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+       git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff a/git-fetch-script b/git-fetch-script
+--- /dev/null
++++ b/git-fetch-script
+@@ -0,0 +1,41 @@
++#!/bin/sh
++#
++merge_repo=$1
++merge_name=${2:-HEAD}
++
++: ${GIT_DIR=.git}
++: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
++
++download_one () {
++      # remote_path="$1" local_file="$2"
++      case "$1" in
++      http://*)
++              wget -q -O "$2" "$1" ;;
++      /*)
++              test -f "$1" && cat >"$2" "$1" ;;
++      *)
++              rsync -L "$1" "$2" ;;
++      esac
++}
++
++download_objects () {
++      # remote_repo="$1" head_sha1="$2"
++      case "$1" in
++      http://*)
++              git-http-pull -a "$2" "$1/"
++              ;;
++      /*)
++              git-local-pull -l -a "$2" "$1/"
++              ;;
++      *)
++              rsync -avz --ignore-existing \
++                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
++              ;;
++      esac
++}
++
++echo "Getting remote $merge_name"
++download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
++
++echo "Getting object database"
++download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+diff a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+-download_one () {
+-      # remote_path="$1" local_file="$2"
+-      case "$1" in
+-      http://*)
+-              wget -q -O "$2" "$1" ;;
+-      /*)
+-              test -f "$1" && cat >"$2" "$1" ;;
+-      *)
+-              rsync -L "$1" "$2" ;;
+-      esac
+-}
+-
+-download_objects () {
+-      # remote_repo="$1" head_sha1="$2"
+-      case "$1" in
+-      http://*)
+-              git-http-pull -a "$2" "$1/"
+-              ;;
+-      /*)
+-              git-local-pull -l -a "$2" "$1/"
+-              ;;
+-      *)
+-              rsync -avz --ignore-existing \
+-                      "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+-              ;;
+-      esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+ git-resolve-script \
+       "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-7.expect b/t/t4100/t-apply-7.expect
new file mode 100644 (file)
index 0000000..1283164
--- /dev/null
@@ -0,0 +1,6 @@
+ Documentation/git-ls-tree.txt |   20 +-
+ ls-tree.c                     |  333 +++++++++++++++++++++++------------------
+ t/t3100-ls-tree-restrict.sh   |    3 
+ tree.c                        |    2 
+ tree.h                        |    1 
+ 5 files changed, 199 insertions(+), 160 deletions(-)
diff --git a/t/t4100/t-apply-7.patch b/t/t4100/t-apply-7.patch
new file mode 100644 (file)
index 0000000..07c6589
--- /dev/null
@@ -0,0 +1,494 @@
+diff a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+ OPTIONS
+ -------
+ <tree-ish>::
+       Id of a tree.
++-d::
++      show only the named tree entry itself, not its children
++
+ -r::
+       recurse into sub-trees
+@@ -28,18 +31,19 @@ OPTIONS
+       \0 line termination on output
+ paths::
+-      Optionally, restrict the output of git-ls-tree to specific
+-      paths. Directories will only list their tree blob ids.
+-      Implies -r.
++      When paths are given, shows them.  Otherwise implicitly
++      uses the root level of the tree as the sole path argument.
++
+ Output Format
+ -------------
+-        <mode>\t      <type>\t        <object>\t      <file>
++        <mode> SP <type> SP <object> TAB <file>
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+ Documentation
+ --------------
+diff a/ls-tree.c b/ls-tree.c
+--- a/ls-tree.c
++++ b/ls-tree.c
+@@ -4,188 +4,217 @@
+  * Copyright (C) Linus Torvalds, 2005
+  */
+ #include "cache.h"
++#include "blob.h"
++#include "tree.h"
+ static int line_termination = '\n';
+-static int recursive = 0;
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
+-struct path_prefix {
+-      struct path_prefix *prev;
+-      const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)       
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+-      int len = 0;
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      len = string_path_prefix(buff,blen,prefix->prev);
+-                      buff += len;
+-                      blen -= len;
+-                      if (blen > 0) {
+-                              *buff = '/';
+-                              len++;
+-                              buff++;
+-                              blen--;
+-                      }
+-              }
+-              strncpy(buff,prefix->name,blen);
+-              return len + strlen(prefix->name);
+-      }
++static struct tree_entry_list root_entry;
+-      return 0;
++static void prepare_root(unsigned char *sha1)
++{
++      unsigned char rsha[20];
++      unsigned long size;
++      void *buf;
++      struct tree *root_tree;
++
++      buf = read_object_with_reference(sha1, "tree", &size, rsha);
++      free(buf);
++      if (!buf)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      root_tree = lookup_tree(rsha);
++      if (!root_tree)
++              die("Could not read %s", sha1_to_hex(sha1));
++
++      /* Prepare a fake entry */
++      root_entry.directory = 1;
++      root_entry.executable = root_entry.symlink = 0;
++      root_entry.mode = S_IFDIR;
++      root_entry.name = "";
++      root_entry.item.tree = root_tree;
++      root_entry.parent = NULL;
+ }
+-static void print_path_prefix(struct path_prefix *prefix)
++static int prepare_children(struct tree_entry_list *elem)
+ {
+-      if (prefix) {
+-              if (prefix->prev) {
+-                      print_path_prefix(prefix->prev);
+-                      putchar('/');
+-              }
+-              fputs(prefix->name, stdout);
++      if (!elem->directory)
++              return -1;
++      if (!elem->item.tree->object.parsed) {
++              struct tree_entry_list *e;
++              if (parse_tree(elem->item.tree))
++                      return -1;
++              /* Set up the parent link */
++              for (e = elem->item.tree->entries; e; e = e->next)
++                      e->parent = elem;
+       }
++      return 0;
+ }
+-/*
+- * return:
+- *    -1 if prefix is *not* a subset of path
+- *     0 if prefix == path
+- *     1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+-      char buff[PATH_MAX];
+-      int len,slen;
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++                                          const char *path,
++                                          const char *path_end)
++{
++      const char *ep;
++      int len;
++
++      while (path < path_end) {
++              if (prepare_children(elem))
++                      return NULL;
+-      if (prefix == NULL)
+-              return 1;
++              /* In elem->tree->entries, find the one that has name
++               * that matches what is between path and ep.
++               */
++              elem = elem->item.tree->entries;
+-      len = string_path_prefix(buff, sizeof buff, prefix);
+-      slen = strlen(path);
++              ep = strchr(path, '/');
++              if (!ep || path_end <= ep)
++                      ep = path_end;
++              len = ep - path;
++
++              while (elem) {
++                      if ((strlen(elem->name) == len) &&
++                          !strncmp(elem->name, path, len))
++                              break;
++                      elem = elem->next;
++              }
++              if (path_end <= ep || !elem)
++                      return elem;
++              while (*ep == '/' && ep < path_end)
++                      ep++;
++              path = ep;
++      }
++      return NULL;
++}
+-      if (slen < len)
+-              return -1;
++static struct tree_entry_list *find_entry(const char *path,
++                                        const char *path_end)
++{
++      /* Find tree element, descending from root, that
++       * corresponds to the named path, lazily expanding
++       * the tree if possible.
++       */
++      if (path == path_end) {
++              /* Special.  This is the root level */
++              return &root_entry;
++      }
++      return find_entry_0(&root_entry, path, path_end);
++}
+-      if (strncmp(path,buff,len) == 0) {
+-              if (slen == len)
+-                      return 0;
+-              else
+-                      return 1;
++static void show_entry_name(struct tree_entry_list *e)
++{
++      /* This is yucky.  The root level is there for
++       * our convenience but we really want to do a
++       * forest.
++       */
++      if (e->parent && e->parent != &root_entry) {
++              show_entry_name(e->parent);
++              putchar('/');
+       }
++      printf("%s", e->name);
++}
+-      return -1;
+-}     
++static const char *entry_type(struct tree_entry_list *e)
++{
++      return (e->directory ? "tree" : "blob");
++}
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+-                         const char *type,
+-                         unsigned long size,
+-                         struct path_prefix *prefix,
+-                         char **match, int matches)
+-{
+-      struct path_prefix this_prefix;
+-      this_prefix.prev = prefix;
+-
+-      if (strcmp(type, "tree"))
+-              die("expected a 'tree' node");
+-
+-      if (matches)
+-              recursive = 1;
+-
+-      while (size) {
+-              int namelen = strlen(buffer)+1;
+-              void *eltbuf = NULL;
+-              char elttype[20];
+-              unsigned long eltsize;
+-              unsigned char *sha1 = buffer + namelen;
+-              char *path = strchr(buffer, ' ') + 1;
+-              unsigned int mode;
+-              const char *matched = NULL;
+-              int mtype = -1;
+-              int mindex;
+-
+-              if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+-                      die("corrupt 'tree' file");
+-              buffer = sha1 + 20;
+-              size -= namelen + 20;
+-
+-              this_prefix.name = path;
+-              for ( mindex = 0; mindex < matches; mindex++) {
+-                      mtype = pathcmp(match[mindex],&this_prefix);
+-                      if (mtype >= 0) {
+-                              matched = match[mindex];
+-                              break;
+-                      }
+-              }
++static const char *entry_hex(struct tree_entry_list *e)
++{
++      return sha1_to_hex(e->directory
++                         ? e->item.tree->object.sha1
++                         : e->item.blob->object.sha1);
++}
+-              /*
+-               * If we're not matching, or if this is an exact match,
+-               * print out the info
+-               */
+-              if (!matches || (matched != NULL && mtype == 0)) {
+-                      printf("%06o %s %s\t", mode,
+-                             S_ISDIR(mode) ? "tree" : "blob",
+-                             sha1_to_hex(sha1));
+-                      print_path_prefix(&this_prefix);
+-                      putchar(line_termination);
+-              }
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
+-              if (! recursive || ! S_ISDIR(mode))
+-                      continue;
++static int show_children(struct tree_entry_list *e, int level)
++{
++      if (prepare_children(e))
++              die("internal error: ls-tree show_children called with non tree");
++      e = e->item.tree->entries;
++      while (e) {
++              show_entry(e, level);
++              e = e->next;
++      }
++      return 0;
++}
+-              if (matches && ! matched)
+-                      continue;
++static int show_entry(struct tree_entry_list *e, int level)
++{
++      int err = 0; 
+-              if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+-                      error("cannot read %s", sha1_to_hex(sha1));
+-                      continue;
+-              }
++      if (e != &root_entry) {
++              printf("%06o %s %s      ", e->mode, entry_type(e),
++                     entry_hex(e));
++              show_entry_name(e);
++              putchar(line_termination);
++      }
+-              /* If this is an exact directory match, we may have
+-               * directory files following this path. Match on them.
+-               * Otherwise, we're at a pach subcomponent, and we need
+-               * to try to match again.
++      if (e->directory) {
++              /* If this is a directory, we have the following cases:
++               * (1) This is the top-level request (explicit path from the
++               *     command line, or "root" if there is no command line).
++               *  a. Without any flag.  We show direct children.  We do not 
++               *     recurse into them.
++               *  b. With -r.  We do recurse into children.
++               *  c. With -d.  We do not recurse into children.
++               * (2) We came here because our caller is either (1-a) or
++               *     (1-b).
++               *  a. Without any flag.  We do not show our children (which
++               *     are grandchildren for the original request).
++               *  b. With -r.  We continue to recurse into our children.
++               *  c. With -d.  We should not have come here to begin with.
+                */
+-              if (mtype == 0)
+-                      mindex++;
+-
+-              list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+-              free(eltbuf);
++              if (level == 0 && !(ls_options & LS_TREE_ONLY))
++                      /* case (1)-a and (1)-b */
++                      err = err | show_children(e, level+1);
++              else if (level && ls_options & LS_RECURSIVE)
++                      /* case (2)-b */
++                      err = err | show_children(e, level+1);
+       }
++      return err;
+ }
+-static int qcmp(const void *a, const void *b)
++static int list_one(const char *path, const char *path_end)
+ {
+-      return strcmp(*(char **)a, *(char **)b);
++      int err = 0;
++      struct tree_entry_list *e = find_entry(path, path_end);
++      if (!e) {
++              /* traditionally ls-tree does not complain about
++               * missing path.  We may change this later to match
++               * what "/bin/ls -a" does, which is to complain.
++               */
++              return err;
++      }
++      err = err | show_entry(e, 0);
++      return err;
+ }
+-static int list(unsigned char *sha1,char **path)
++static int list(char **path)
+ {
+-      void *buffer;
+-      unsigned long size;
+-      int npaths;
+-
+-      for (npaths = 0; path[npaths] != NULL; npaths++)
+-              ;
+-
+-      qsort(path,npaths,sizeof(char *),qcmp);
+-
+-      buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+-      if (!buffer)
+-              die("unable to read sha1 file");
+-      list_recursive(buffer, "tree", size, NULL, path, npaths);
+-      free(buffer);
+-      return 0;
++      int i;
++      int err = 0;
++      for (i = 0; path[i]; i++) {
++              int len = strlen(path[i]);
++              while (0 <= len && path[i][len] == '/')
++                      len--;
++              err = err | list_one(path[i], path[i] + len);
++      }
++      return err;
+ }
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
++static const char *ls_tree_usage =
++      "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
+ int main(int argc, char **argv)
+ {
++      static char *path0[] = { "", NULL };
++      char **path;
+       unsigned char sha1[20];
+       while (1 < argc && argv[1][0] == '-') {
+@@ -194,7 +223,10 @@ int main(int argc, char **argv)
+                       line_termination = 0;
+                       break;
+               case 'r':
+-                      recursive = 1;
++                      ls_options |= LS_RECURSIVE;
++                      break;
++              case 'd':
++                      ls_options |= LS_TREE_ONLY;
+                       break;
+               default:
+                       usage(ls_tree_usage);
+@@ -206,7 +238,10 @@ int main(int argc, char **argv)
+               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1) < 0)
+               usage(ls_tree_usage);
+-      if (list(sha1, &argv[2]) < 0)
++
++      path = (argc == 2) ? path0 : (argv + 2);
++      prepare_root(sha1);
++      if (list(path) < 0)
+               die("list failed");
+       return 0;
+ }
+diff a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+     'ls-tree filtered' \
+     'git-ls-tree $tree path1 path0 >current &&
+      cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+      test_output'
+@@ -85,7 +85,6 @@ test_expect_success \
+      cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+               }
+               if (obj)
+                       add_ref(&item->object, obj);
+-
++              entry->parent = NULL; /* needs to be filled by the user */
+               *list_p = entry;
+               list_p = &entry->next;
+       }
+diff a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+               struct tree *tree;
+               struct blob *blob;
+       } item;
++      struct tree_entry_list *parent;
+ };
+ struct tree {
diff --git a/t/t4101-apply-nonl.sh b/t/t4101-apply-nonl.sh
new file mode 100755 (executable)
index 0000000..26b131d
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply should handle files with incomplete lines.
+
+'
+. ./test-lib.sh
+
+# setup
+
+(echo a; echo b) >frotz.0
+(echo a; echo b; echo c) >frotz.1
+(echo a; echo b | tr -d '\012') >frotz.2
+(echo a; echo c; echo b | tr -d '\012') >frotz.3
+
+for i in 0 1 2 3
+do
+  for j in 0 1 2 3
+  do
+    test $i -eq $j && continue
+    diff -u frotz.$i frotz.$j |
+    sed -e '
+       /^---/s|.*|--- a/frotz|
+       /^+++/s|.*|+++ b/frotz|' >diff.$i-$j
+    cat frotz.$i >frotz
+    test_expect_success \
+        "apply diff between $i and $j" \
+       "git-apply <diff.$i-$j && diff frotz.$j frotz"
+  done
+done
+
+test_done
diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh
new file mode 100755 (executable)
index 0000000..fbb508d
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply handling copy/rename patch.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat >test-patch <<\EOF
+diff --git a/foo b/bar
+similarity index 47%
+copy from foo
+copy to bar
+--- a/foo
++++ b/bar
+@@ -1 +1 @@
+-This is foo
++This is bar
+EOF
+
+echo 'This is foo' >foo
+chmod +x foo
+
+test_expect_success setup \
+    'git-update-index --add foo'
+
+test_expect_success apply \
+    'git-apply --index --stat --summary --apply test-patch'
+
+if [ "$(git repo-config --get core.filemode)" = false ]
+then
+       say 'filemode disabled on the filesystem'
+else
+       test_expect_success validate \
+           'test -f bar && ls -l bar | grep "^-..x......"'
+fi
+
+test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
new file mode 100755 (executable)
index 0000000..00bd8b1
--- /dev/null
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply handling binary patches
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat >file1 <<EOF
+A quick brown fox jumps over the lazy dog.
+A tiny little penguin runs around in circles.
+There is a flag with Linux written on it.
+A slow black-and-white panda just sits there,
+munching on his bamboo.
+EOF
+cat file1 >file2
+cat file1 >file4
+
+git-update-index --add --remove file1 file2 file4
+git-commit -m 'Initial Version' 2>/dev/null
+
+git-checkout -b binary
+tr 'x' '\0' <file1 >file3
+cat file3 >file4
+git-add file2
+tr '\0' 'v' <file3 >file1
+rm -f file2
+git-update-index --add --remove file1 file2 file3 file4
+git-commit -m 'Second Version'
+
+git-diff-tree -p master binary >B.diff
+git-diff-tree -p -C master binary >C.diff
+
+git-diff-tree -p --full-index master binary >BF.diff
+git-diff-tree -p --full-index -C master binary >CF.diff
+
+test_expect_success 'stat binary diff -- should not fail.' \
+       'git-checkout master
+        git-apply --stat --summary B.diff'
+
+test_expect_success 'stat binary diff (copy) -- should not fail.' \
+       'git-checkout master
+        git-apply --stat --summary C.diff'
+
+test_expect_failure 'check binary diff -- should fail.' \
+       'git-checkout master
+        git-apply --check B.diff'
+
+test_expect_failure 'check binary diff (copy) -- should fail.' \
+       'git-checkout master
+        git-apply --check C.diff'
+
+test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement B.diff'
+
+test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement C.diff'
+
+test_expect_success 'check binary diff with replacement.' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement BF.diff'
+
+test_expect_success 'check binary diff with replacement (copy).' \
+       'git-checkout master
+        git-apply --check --allow-binary-replacement CF.diff'
+
+# Now we start applying them.
+
+do_reset () {
+       rm -f file?
+       git-reset --hard
+       git-checkout -f master
+}
+
+test_expect_failure 'apply binary diff -- should fail.' \
+       'do_reset
+        git-apply B.diff'
+
+test_expect_failure 'apply binary diff -- should fail.' \
+       'do_reset
+        git-apply --index B.diff'
+
+test_expect_failure 'apply binary diff (copy) -- should fail.' \
+       'do_reset
+        git-apply C.diff'
+
+test_expect_failure 'apply binary diff (copy) -- should fail.' \
+       'do_reset
+        git-apply --index C.diff'
+
+test_expect_failure 'apply binary diff without replacement -- should fail.' \
+       'do_reset
+        git-apply BF.diff'
+
+test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \
+       'do_reset
+        git-apply CF.diff'
+
+test_expect_success 'apply binary diff.' \
+       'do_reset
+        git-apply --allow-binary-replacement --index BF.diff &&
+        test -z "$(git-diff --name-status binary)"'
+
+test_expect_success 'apply binary diff (copy).' \
+       'do_reset
+        git-apply --allow-binary-replacement --index CF.diff &&
+        test -z "$(git-diff --name-status binary)"'
+
+test_done
diff --git a/t/t4109-apply-multifrag.sh b/t/t4109-apply-multifrag.sh
new file mode 100755 (executable)
index 0000000..5988e1a
--- /dev/null
@@ -0,0 +1,176 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-apply test patches with multiple fragments.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat > patch1.patch <<\EOF
+diff --git a/main.c b/main.c
+new file mode 100644
+--- /dev/null
++++ b/main.c
+@@ -0,0 +1,23 @@
++#include <stdio.h>
++
++int func(int num);
++void print_int(int num);
++
++int main() {
++      int i;
++
++      for (i = 0; i < 10; i++) {
++              print_int(func(i));
++      }
++
++      return 0;
++}
++
++int func(int num) {
++      return num * num;
++}
++
++void print_int(int num) {
++      printf("%d", num);
++}
++
+EOF
+cat > patch2.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,7 +1,9 @@
++#include <stdlib.h>
+ #include <stdio.h>
+ int func(int num);
+ void print_int(int num);
++void print_ln();
+ int main() {
+       int i;
+@@ -10,6 +12,8 @@
+               print_int(func(i));
+       }
++      print_ln();
++
+       return 0;
+ }
+@@ -21,3 +25,7 @@
+       printf("%d", num);
+ }
++void print_ln() {
++      printf("\n");
++}
++
+EOF
+cat > patch3.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,9 +1,7 @@
+-#include <stdlib.h>
+ #include <stdio.h>
+ int func(int num);
+ void print_int(int num);
+-void print_ln();
+ int main() {
+       int i;
+@@ -12,8 +10,6 @@
+               print_int(func(i));
+       }
+-      print_ln();
+-
+       return 0;
+ }
+@@ -25,7 +21,3 @@
+       printf("%d", num);
+ }
+-void print_ln() {
+-      printf("\n");
+-}
+-
+EOF
+cat > patch4.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,13 +1,14 @@
+ #include <stdio.h>
+ int func(int num);
+-void print_int(int num);
++int func2(int num);
+ int main() {
+       int i;
+       for (i = 0; i < 10; i++) {
+-              print_int(func(i));
++              printf("%d", func(i));
++              printf("%d", func3(i));
+       }
+       return 0;
+@@ -17,7 +18,7 @@
+       return num * num;
+ }
+-void print_int(int num) {
+-      printf("%d", num);
++int func2(int num) {
++      return num * num * num;
+ }
+EOF
+
+test_expect_success "S = git-apply (1)" \
+    'git-apply patch1.patch patch2.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (1)" \
+    'cat patch1.patch patch2.patch | patch -p1'
+
+test_expect_success "S = cmp (1)" \
+    'cmp main.c.git main.c'
+
+rm -f main.c main.c.git
+
+test_expect_success "S = git-apply (2)" \
+    'git-apply patch1.patch patch2.patch patch3.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (2)" \
+    'cat patch1.patch patch2.patch patch3.patch | patch -p1'
+
+test_expect_success "S = cmp (2)" \
+    'cmp main.c.git main.c'
+
+rm -f main.c main.c.git
+
+test_expect_success "S = git-apply (3)" \
+    'git-apply patch1.patch patch4.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (3)" \
+    'cat patch1.patch patch4.patch | patch -p1'
+
+test_expect_success "S = cmp (3)" \
+    'cmp main.c.git main.c'
+
+test_done
+
diff --git a/t/t4110-apply-scan.sh b/t/t4110-apply-scan.sh
new file mode 100755 (executable)
index 0000000..005f744
--- /dev/null
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-apply test for patches which require scanning forwards and backwards.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat > patch1.patch <<\EOF
+diff --git a/new.txt b/new.txt
+new file mode 100644
+--- /dev/null
++++ b/new.txt
+@@ -0,0 +1,12 @@
++a1
++a11
++a111
++a1111
++b1
++b11
++b111
++b1111
++c1
++c11
++c111
++c1111
+EOF
+cat > patch2.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -1,7 +1,3 @@
+-a1
+-a11
+-a111
+-a1111
+ b1
+ b11
+ b111
+EOF
+cat > patch3.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -6,6 +6,10 @@
+ b11
+ b111
+ b1111
++b2
++b22
++b222
++b2222
+ c1
+ c11
+ c111
+EOF
+cat > patch4.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -1,3 +1,7 @@
++a1
++a11
++a111
++a1111
+ b1
+ b11
+ b111
+EOF
+cat > patch5.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -10,3 +10,7 @@
+ c11
+ c111
+ c1111
++c2
++c22
++c222
++c2222
+EOF
+
+test_expect_success "S = git-apply scan" \
+    'git-apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
+mv new.txt apply.txt
+
+test_expect_success "S = patch scan" \
+    'cat patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch | patch'
+mv new.txt patch.txt
+
+test_expect_success "S = cmp" \
+    'cmp apply.txt patch.txt'
+
+test_done
+
diff --git a/t/t4112-apply-renames.sh b/t/t4112-apply-renames.sh
new file mode 100755 (executable)
index 0000000..a06f695
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply should not get confused with rename/copy.
+
+'
+
+. ./test-lib.sh
+
+# setup
+
+mkdir -p include/arch/x86_64/klibc klibc/arch/x86_64/include/klibc
+
+cat >include/arch/x86_64/klibc/archsetjmp.h <<\EOF
+/*
+ * arch/x86_64/include/klibc/archsetjmp.h
+ */
+
+#ifndef _KLIBC_ARCHSETJMP_H
+#define _KLIBC_ARCHSETJMP_H
+
+struct __jmp_buf {
+  unsigned long __rbx;
+  unsigned long __rsp;
+  unsigned long __rbp;
+  unsigned long __r12;
+  unsigned long __r13;
+  unsigned long __r14;
+  unsigned long __r15;
+  unsigned long __rip;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+
+#endif /* _SETJMP_H */
+EOF
+
+cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF
+/*
+ * arch/x86_64/include/klibc/archsetjmp.h
+ */
+
+#ifndef _KLIBC_ARCHSETJMP_H
+#define _KLIBC_ARCHSETJMP_H
+
+struct __jmp_buf {
+  unsigned long __rbx;
+  unsigned long __rsp;
+  unsigned long __rbp;
+  unsigned long __r12;
+  unsigned long __r13;
+  unsigned long __r14;
+  unsigned long __r15;
+  unsigned long __rip;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+
+#endif /* _SETJMP_H */
+EOF
+
+cat >patch <<\EOF
+diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/cris/klibc/archsetjmp.h
+similarity index 76%
+copy from klibc/arch/x86_64/include/klibc/archsetjmp.h
+copy to include/arch/cris/klibc/archsetjmp.h
+--- a/klibc/arch/x86_64/include/klibc/archsetjmp.h
++++ b/include/arch/cris/klibc/archsetjmp.h
+@@ -1,21 +1,24 @@
+ /*
+- * arch/x86_64/include/klibc/archsetjmp.h
++ * arch/cris/include/klibc/archsetjmp.h
+  */
+ #ifndef _KLIBC_ARCHSETJMP_H
+ #define _KLIBC_ARCHSETJMP_H
+ struct __jmp_buf {
+-  unsigned long __rbx;
+-  unsigned long __rsp;
+-  unsigned long __rbp;
+-  unsigned long __r12;
+-  unsigned long __r13;
+-  unsigned long __r14;
+-  unsigned long __r15;
+-  unsigned long __rip;
++  unsigned long __r0;
++  unsigned long __r1;
++  unsigned long __r2;
++  unsigned long __r3;
++  unsigned long __r4;
++  unsigned long __r5;
++  unsigned long __r6;
++  unsigned long __r7;
++  unsigned long __r8;
++  unsigned long __sp;
++  unsigned long __srp;
+ };
+ typedef struct __jmp_buf jmp_buf[1];
+-#endif /* _SETJMP_H */
++#endif /* _KLIBC_ARCHSETJMP_H */
+diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/m32r/klibc/archsetjmp.h
+similarity index 66%
+rename from klibc/arch/x86_64/include/klibc/archsetjmp.h
+rename to include/arch/m32r/klibc/archsetjmp.h
+--- a/klibc/arch/x86_64/include/klibc/archsetjmp.h
++++ b/include/arch/m32r/klibc/archsetjmp.h
+@@ -1,21 +1,21 @@
+ /*
+- * arch/x86_64/include/klibc/archsetjmp.h
++ * arch/m32r/include/klibc/archsetjmp.h
+  */
+ #ifndef _KLIBC_ARCHSETJMP_H
+ #define _KLIBC_ARCHSETJMP_H
+ struct __jmp_buf {
+-  unsigned long __rbx;
+-  unsigned long __rsp;
+-  unsigned long __rbp;
++  unsigned long __r8;
++  unsigned long __r9;
++  unsigned long __r10;
++  unsigned long __r11;
+   unsigned long __r12;
+   unsigned long __r13;
+   unsigned long __r14;
+   unsigned long __r15;
+-  unsigned long __rip;
+ };
+ typedef struct __jmp_buf jmp_buf[1];
+-#endif /* _SETJMP_H */
++#endif /* _KLIBC_ARCHSETJMP_H */
+EOF
+
+find include klibc -type f -print | xargs git-update-index --add --
+
+test_expect_success 'check rename/copy patch' 'git-apply --check patch'
+
+test_expect_success 'apply rename/copy patch' 'git-apply --index patch'
+
+test_done
diff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh
new file mode 100755 (executable)
index 0000000..7fd0cf6
--- /dev/null
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Catalin Marinas
+#
+
+test_description='git-apply trying to add an ending line.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat >test-patch <<\EOF
+diff --git a/file b/file
+--- a/file
++++ b/file
+@@ -1,2 +1,3 @@
+ a
+ b
++c
+EOF
+
+echo 'a' >file
+echo 'b' >>file
+echo 'c' >>file
+
+test_expect_success setup \
+    'git-update-index --add file'
+
+# test
+
+test_expect_failure 'apply at the end' \
+    'git-apply --index test-patch'
+
+cat >test-patch <<\EOF
+diff a/file b/file
+--- a/file
++++ b/file
+@@ -1,2 +1,3 @@
++a
+ b
+ c
+EOF
+
+echo >file 'a
+b
+c'
+git-update-index file
+
+test_expect_failure 'apply at the beginning' \
+       'git-apply --index test-patch'
+
+test_done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
new file mode 100755 (executable)
index 0000000..278eb66
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-tar-tree and git-get-tar-commit-id test
+
+This test covers the topics of file contents, commit date handling and
+commit id embedding:
+
+  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
+  used.
+
+  git-tar-tree applies the commit date to every file in the archive it
+  creates.  The test sets the commit date to a specific value and checks
+  if the tar archive contains that value.
+
+  When giving git-tar-tree a commit id (in contrast to a tree id) it
+  embeds this commit id into the tar archive as a comment.  The test
+  checks the ability of git-get-tar-commit-id to figure it out from the
+  tar file.
+
+'
+
+. ./test-lib.sh
+TAR=${TAR:-tar}
+
+test_expect_success \
+    'populate workdir' \
+    'mkdir a b c &&
+     echo simple textfile >a/a &&
+     mkdir a/bin &&
+     cp /bin/sh a/bin &&
+     ln -s a a/l1 &&
+     (p=long_path_to_a_file && cd a &&
+      for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
+      echo text >file_with_long_path) &&
+     (cd a && find .) | sort >a.lst'
+
+test_expect_success \
+    'add files to repository' \
+    'find a -type f | xargs git-update-index --add &&
+     find a -type l | xargs git-update-index --add &&
+     treeid=`git-write-tree` &&
+     echo $treeid >treeid &&
+     git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+     git-commit-tree $treeid </dev/null)'
+
+test_expect_success \
+    'git-tar-tree' \
+    'git-tar-tree HEAD >b.tar'
+
+test_expect_success \
+    'validate file modification time' \
+    'TZ=GMT $TAR tvf b.tar a/a |
+     awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \
+     >b.mtime &&
+     echo "2005-05-27 22:00:00" >expected.mtime &&
+     diff expected.mtime b.mtime'
+
+test_expect_success \
+    'git-get-tar-commit-id' \
+    'git-get-tar-commit-id <b.tar >b.commitid &&
+     diff .git/$(git-symbolic-ref HEAD) b.commitid'
+
+test_expect_success \
+    'extract tar archive' \
+    '(cd b && $TAR xf -) <b.tar'
+
+test_expect_success \
+    'validate filenames' \
+    '(cd b/a && find .) | sort >b.lst &&
+     diff a.lst b.lst'
+
+test_expect_success \
+    'validate file contents' \
+    'diff -r a b/a'
+
+test_expect_success \
+    'git-tar-tree with prefix' \
+    'git-tar-tree HEAD prefix >c.tar'
+
+test_expect_success \
+    'extract tar archive with prefix' \
+    '(cd c && $TAR xf -) <c.tar'
+
+test_expect_success \
+    'validate filenames with prefix' \
+    '(cd c/prefix/a && find .) | sort >c.lst &&
+     diff a.lst c.lst'
+
+test_expect_success \
+    'validate file contents with prefix' \
+    'diff -r a c/prefix/a'
+
+test_done
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
new file mode 100755 (executable)
index 0000000..de45ac4
--- /dev/null
@@ -0,0 +1,199 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-pack-object
+
+'
+. ./test-lib.sh
+
+TRASH=`pwd`
+
+test_expect_success \
+    'setup' \
+    'rm -f .git/index*
+     for i in a b c
+     do
+            dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
+            git-update-index --add $i || return 1
+     done &&
+     cat c >d && echo foo >>d && git-update-index --add d &&
+     tree=`git-write-tree` &&
+     commit=`git-commit-tree $tree </dev/null` && {
+        echo $tree &&
+        echo $commit &&
+        git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
+     } >obj-list && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || return 1
+        done <obj-list
+     } >expect'
+
+test_expect_success \
+    'pack without delta' \
+    'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'unpack without delta' \
+    "GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     git-unpack-objects -n <test-1-${packname_1}.pack &&
+     git-unpack-objects <test-1-${packname_1}.pack"
+
+unset GIT_OBJECT_DIRECTORY
+cd "$TRASH/.git2"
+
+test_expect_success \
+    'check unpack without delta' \
+    '(cd ../.git && find objects -type f -print) |
+     while read path
+     do
+         cmp $path ../.git/$path || {
+            echo $path differs.
+            return 1
+        }
+     done'
+cd "$TRASH"
+
+test_expect_success \
+    'pack with delta' \
+    'pwd &&
+     packname_2=$(git-pack-objects test-2 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'unpack with delta' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     git-unpack-objects -n <test-2-${packname_2}.pack &&
+     git-unpack-objects <test-2-${packname_2}.pack'
+
+unset GIT_OBJECT_DIRECTORY
+cd "$TRASH/.git2"
+test_expect_success \
+    'check unpack with delta' \
+    '(cd ../.git && find objects -type f -print) |
+     while read path
+     do
+         cmp $path ../.git/$path || {
+            echo $path differs.
+            return 1
+        }
+     done'
+cd "$TRASH"
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+    'use packed objects' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     git-init-db &&
+     cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || return 1
+        done <obj-list
+    } >current &&
+    diff expect current'
+
+
+test_expect_success \
+    'use packed deltified objects' \
+    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+     export GIT_OBJECT_DIRECTORY &&
+     rm -f .git2/objects/pack/test-?.idx &&
+     cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
+        git-diff-tree --root -p $commit &&
+        while read object
+        do
+           t=`git-cat-file -t $object` &&
+           git-cat-file $t $object || return 1
+        done <obj-list
+    } >current &&
+    diff expect current'
+
+unset GIT_OBJECT_DIRECTORY
+
+test_expect_success \
+    'verify pack' \
+    'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx'
+
+test_expect_success \
+    'corrupt a pack and see if verify catches' \
+    'cp test-1-${packname_1}.idx test-3.idx &&
+     cp test-2-${packname_2}.pack test-3.pack &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     : PACK_SIGNATURE &&
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     : PACK_VERSION &&
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     : TYPE/SIZE byte of the first packed object data &&
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     : sum of the index file itself &&
+     l=`wc -c <test-3.idx` &&
+     l=`expr $l - 20` &&
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
+     if git-verify-pack test-3.pack
+     then false
+     else :;
+     fi &&
+
+     :'
+
+test_expect_success \
+    'build pack index for an existing pack' \
+    'cp test-1-${packname_1}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-3.pack &&
+     cmp tmp.idx test-1-${packname_1}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-1-${packname_1}.idx &&
+
+     cp test-2-${packname_2}.pack test-3.pack &&
+     git-index-pack -o tmp.idx test-2-${packname_2}.pack &&
+     cmp tmp.idx test-2-${packname_2}.idx &&
+
+     git-index-pack test-3.pack &&
+     cmp test-3.idx test-2-${packname_2}.idx &&
+
+     :'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
new file mode 100755 (executable)
index 0000000..f3694ac
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+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'
+test_expect_success setup '
+       tree=$(git-write-tree) &&
+       commit=$(echo "Commit #0" | git-commit-tree $tree) &&
+       zero=$commit &&
+       parent=$zero &&
+       for i in $cnt
+       do
+           sleep 1 &&
+           commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
+           parent=$commit || return 1
+       done &&
+       git-update-ref HEAD "$commit" &&
+       git-clone -l ./. victim &&
+       cd victim &&
+       git-log &&
+       cd .. &&
+       git-update-ref HEAD "$zero" &&
+       parent=$zero &&
+       for i in $cnt
+       do
+           sleep 1 &&
+           commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
+           parent=$commit || return 1
+       done &&
+       git-update-ref HEAD "$commit" &&
+       echo Rebase &&
+       git-log'
+
+test_expect_success \
+        'pushing rewound head should not barf but require --force' ' 
+       # should not fail but refuse to update.
+       if git-send-pack ./victim/.git/ master
+       then
+               # now it should fail with Pasky patch
+               echo >&2 Gaah, it should have failed.
+               false
+       else
+               echo >&2 Thanks, it correctly failed.
+               true
+       fi &&
+       if cmp victim/.git/refs/heads/master .git/refs/heads/master
+       then
+               # should have been left as it was!
+               false
+       else
+               true
+       fi &&
+       # this should update
+       git-send-pack --force ./victim/.git/ master &&
+       cmp victim/.git/refs/heads/master .git/refs/heads/master
+'
+
+test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
new file mode 100755 (executable)
index 0000000..f7625a6
--- /dev/null
@@ -0,0 +1,131 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Testing multi_ack pack fetching
+
+'
+. ./test-lib.sh
+
+# Test fetch-pack/upload-pack pair.
+
+# Some convenience functions
+
+add () {
+       name=$1
+       text="$@"
+       branch=`echo $name | sed -e 's/^\(.\).*$/\1/'`
+       parents=""
+
+       shift
+       while test $1; do
+               parents="$parents -p $1"
+               shift
+       done
+
+       echo "$text" > test.txt
+       git-update-index --add test.txt
+       tree=$(git-write-tree)
+       # make sure timestamps are in correct order
+       sec=$(($sec+1))
+       commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
+               git-commit-tree $tree $parents 2>>log2.txt)
+       export $name=$commit
+       echo $commit > .git/refs/heads/$branch
+       eval ${branch}TIP=$commit
+}
+
+count_objects () {
+       ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
+}
+
+test_expect_object_count () {
+       message=$1
+       count=$2
+
+       output="$(count_objects)"
+       test_expect_success \
+               "new object count $message" \
+               "test $count = $output"
+}
+
+pull_to_client () {
+       number=$1
+       heads=$2
+       count=$3
+       no_strict_count_check=$4
+
+       cd client
+       test_expect_success "$number pull" \
+               "git-fetch-pack -k -v .. $heads"
+       case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
+       case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
+       git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
+
+       test_expect_success "fsck" 'git-fsck-objects --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'
+
+       test_expect_success "new object count after $number pull" \
+       'idx=`echo pack-*.idx` &&
+        pack_count=`git-show-index <$idx | wc -l` &&
+        test $pack_count = $count'
+       test -z "$pack_count" && pack_count=0
+       if [ -z "$no_strict_count_check" ]; then
+               test_expect_success "minimal count" "test $count = $pack_count"
+       else
+               test $count != $pack_count && \
+                       echo "WARNING: $pack_count objects transmitted, only $count of which were needed"
+       fi
+       rm -f pack-*
+       cd ..
+}
+
+# Here begins the actual testing
+
+# A1 - ... - A20 - A21
+#    \
+#      B1  -   B2 - .. - B70
+
+# client pulls A20, B1. Then tracks only B. Then pulls A.
+
+(
+       mkdir client &&
+       cd client &&
+       git-init-db 2>> log2.txt
+)
+
+add A1
+
+prev=1; cur=2; while [ $cur -le 10 ]; do
+       add A$cur $(eval echo \$A$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+add B1 $A1
+
+echo $ATIP > .git/refs/heads/A
+echo $BTIP > .git/refs/heads/B
+git-symbolic-ref HEAD refs/heads/B
+
+pull_to_client 1st "B A" $((11*3))
+
+add A11 $A10
+
+prev=1; cur=2; while [ $cur -le 65 ]; do
+       add B$cur $(eval echo \$B$prev)
+       prev=$cur
+       cur=$(($cur+1))
+done
+
+pull_to_client 2nd "B" $((64*3))
+
+pull_to_client 3rd "A" $((1*3)) # old fails
+
+test_done
diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh
new file mode 100755 (executable)
index 0000000..0c6a363
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Carl D. Worth <cworth@cworth.org>
+#
+
+test_description='test git-clone to cleanup after failure
+
+This test covers the fact that if git-clone fails, it should remove
+the directory it created, to avoid the user having to manually
+remove the directory before attempting a clone again.'
+
+. ./test-lib.sh
+
+test_expect_failure \
+    'clone of non-existent source should fail' \
+    'git-clone foo bar'
+
+test_expect_failure \
+    'failed clone should not leave a directory' \
+    'cd bar'
+
+# Need a repo to clone
+test_create_repo foo
+
+# clone doesn't like it if there is no HEAD. Is that a bug?
+(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1)
+
+test_expect_success \
+    'clone should work now that source exists' \
+    'git-clone foo bar'
+
+test_expect_success \
+    'successfull clone must leave the directory' \
+    'cd bar'
+
+test_done
diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh
new file mode 100755 (executable)
index 0000000..916ee15
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=`pwd`
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo first > file1 &&
+git add file1 &&
+git commit -m initial'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone A B && cd B &&
+echo second > file2 &&
+git add file2 &&
+git commit -m addition &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'cloning with reference' \
+'git clone -l -s --reference B A C'
+
+cd "$base_dir"
+
+test_expect_success 'existance of info/alternates' \
+'test `wc -l <C/.git/objects/info/alternates` = 2'
+
+cd "$base_dir"
+
+test_expect_success 'pulling from reference' \
+'cd C &&
+git pull ../B'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used' \
+'cd C &&
+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 &&
+git add file3 &&
+git commit -m update &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'pulling changes from origin' \
+'cd C &&
+git pull origin'
+
+cd "$base_dir"
+
+# the 2 local objects are commit and tree from the merge
+test_expect_success 'that alternate to origin gets used' \
+'cd C &&
+echo "2 objects" > expected &&
+git count-objects | cut -d, -f1 > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_done
diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh
new file mode 100755 (executable)
index 0000000..097d037
--- /dev/null
@@ -0,0 +1,105 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test transitive info/alternate entries'
+. ./test-lib.sh
+
+# test that a file is not reachable in the current repository
+# but that it is after creating a info/alternate entry
+reachable_via() {
+       alternate="$1"
+       file="$2"
+       if git cat-file -e "HEAD:$file"; then return 1; fi
+       echo "$alternate" >> .git/objects/info/alternate
+       git cat-file -e "HEAD:$file"
+}
+
+test_valid_repo() {
+       git fsck-objects --full > fsck.log &&
+       test `wc -l < fsck.log` = 0
+}
+
+base_dir=`pwd`
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo "Hello World" > file1 &&
+git add file1 &&
+git commit -m "Initial commit" file1 &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone -l -s A B && cd B &&
+echo "foo bar" > file2 &&
+git add file2 &&
+git commit -m "next commit" file2 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing third repository' \
+'git clone -l -s B C && cd C &&
+echo "Goodbye, cruel world" > file3 &&
+git add file3 &&
+git commit -m "one more" file3 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_failure 'creating too deep nesting' \
+'git clone -l -s C D &&
+git clone -l -s D E &&
+git clone -l -s E F &&
+git clone -l -s F G &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of third repository' \
+'cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of fourth repository' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'breaking of loops' \
+"echo '$base_dir/B/.git/objects' >> '$base_dir'/A/.git/objects/info/alternates&&
+cd C &&
+test_valid_repo"
+
+cd "$base_dir"
+
+test_expect_failure 'that info/alternates is neccessary' \
+'cd C &&
+rm .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'that relative alternate is possible for current dir' \
+'cd C &&
+echo "../../../B/.git/objects" > .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_failure 'that relative alternate is only possible for current dir' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_done
+
diff --git a/t/t6000lib.sh b/t/t6000lib.sh
new file mode 100755 (executable)
index 0000000..d402621
--- /dev/null
@@ -0,0 +1,116 @@
+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
+
+:> sed.script
+
+# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags
+tag()
+{
+       _tag=$1
+       [ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist"
+       cat .git/refs/tags/$_tag
+}
+
+# Generate a commit using the text specified to make it unique and the tree
+# named by the tag specified.
+unique_commit()
+{
+       _text=$1
+        _tree=$2
+       shift 2
+       echo $_text | git-commit-tree $(tag $_tree) "$@"
+}
+
+# Save the output of a command into the tag specified. Prepend
+# a substitution script for the tag onto the front of sed.script
+save_tag()
+{
+       _tag=$1 
+       [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
+       shift 1
+       "$@" >.git/refs/tags/$_tag
+
+        echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp
+       cat sed.script >> sed.script.tmp
+       rm sed.script
+       mv sed.script.tmp sed.script
+}
+
+# Replace unhelpful sha1 hashses with their symbolic equivalents 
+entag()
+{
+       sed -f sed.script
+}
+
+# Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL
+# tag to a specified value. Restore the original value on return.
+as_author()
+{
+       _author=$1
+       shift 1
+        _save=$GIT_AUTHOR_EMAIL
+
+       export GIT_AUTHOR_EMAIL="$_author"
+       "$@"
+       if test -z "$_save"
+       then
+               unset GIT_AUTHOR_EMAIL
+       else
+               export GIT_AUTHOR_EMAIL="$_save"
+       fi
+}
+
+commit_date()
+{
+        _commit=$1
+       git-cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" 
+}
+
+on_committer_date()
+{
+    _date=$1
+    shift 1
+    export GIT_COMMITTER_DATE="$_date"
+    "$@"
+    unset GIT_COMMITTER_DATE
+}
+
+# Execute a command and suppress any error output.
+hide_error()
+{
+       "$@" 2>/dev/null
+}
+
+check_output()
+{
+       _name=$1
+       shift 1
+       if eval "$*" | entag > $_name.actual
+       then
+               diff $_name.expected $_name.actual
+       else
+               return 1;
+       fi
+}
+
+# Turn a reasonable test description into a reasonable test name.
+# All alphanums translated into -'s which are then compressed and stripped
+# from front and back.
+name_from_description()
+{
+        tr "'" '-' | tr '~`!@#$%^&*()_+={}[]|\;:"<>,/? ' '-' | tr -s '-' | tr '[A-Z]' '[a-z]' | sed "s/^-*//;s/-*\$//"
+}
+
+
+# Execute the test described by the first argument, by eval'ing
+# command line specified in the 2nd argument. Check the status code
+# is zero and that the output matches the stream read from 
+# stdin.
+test_output_expect_success()
+{      
+       _description=$1
+        _test=$2
+        [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
+        _name=$(echo $_description | name_from_description)
+       cat > $_name.expected
+       test_expect_success "$_description" "check_output $_name \"$_test\"" 
+}
diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh
new file mode 100755 (executable)
index 0000000..693de9b
--- /dev/null
@@ -0,0 +1,238 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+test_description='Tests git-rev-list --bisect functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+# usage: test_bisection max-diff bisect-option head ^prune...
+#
+# e.g. test_bisection 1 --bisect l1 ^l0
+#
+test_bisection_diff()
+{
+       _max_diff=$1
+       _bisect_option=$2
+       shift 2
+       _bisection=$(git-rev-list $_bisect_option "$@")
+       _list_size=$(git-rev-list "$@" | wc -l)
+        _head=$1
+       shift 1
+       _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
+       [ -n "$_list_size" -a -n "$_bisection_size" ] ||
+       error "test_bisection_diff failed"
+
+       # Test if bisection size is close to half of list size within
+       # tolerance.
+       # 
+       _bisect_err=`expr $_list_size - $_bisection_size \* 2`
+       test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
+       _bisect_err=`expr $_bisect_err / 2` ; # floor
+
+       test_expect_success \
+       "bisection diff $_bisect_option $_head $* <= $_max_diff" \
+       'test $_bisect_err -le $_max_diff'
+}
+
+date >path0
+git-update-index --add path0
+save_tag tree git-write-tree
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
+on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
+on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
+on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
+git-update-ref HEAD $(tag l5)
+
+
+#     E
+#    / \
+#   e1  |
+#   |   |
+#   e2  |
+#   |   |
+#   e3  |
+#   |   |
+#   e4  |
+#   |   |
+#   |   f1
+#   |   |
+#   |   f2
+#   |   |
+#   |   f3
+#   |   |
+#   |   f4
+#   |   |
+#   e5  |
+#   |   |
+#   e6  |
+#   |   |
+#   e7  |
+#   |   |
+#   e8  |
+#    \ /
+#     F
+
+
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree
+on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F
+on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8
+on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7
+on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6
+on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F
+on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4
+on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3
+on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2
+on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5
+on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4
+on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3
+on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2
+on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1
+
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree
+on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U
+on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0
+on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0
+on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0
+on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0
+on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0
+on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
+
+test_sequence()
+{
+       _bisect_option=$1       
+       
+       test_bisection_diff 0 $_bisect_option l0 ^root
+       test_bisection_diff 0 $_bisect_option l1 ^root
+       test_bisection_diff 0 $_bisect_option l2 ^root
+       test_bisection_diff 0 $_bisect_option a0 ^root
+       test_bisection_diff 0 $_bisect_option a1 ^root
+       test_bisection_diff 0 $_bisect_option a2 ^root
+       test_bisection_diff 0 $_bisect_option a3 ^root
+       test_bisection_diff 0 $_bisect_option b1 ^root
+       test_bisection_diff 0 $_bisect_option b2 ^root
+       test_bisection_diff 0 $_bisect_option b3 ^root
+       test_bisection_diff 0 $_bisect_option c1 ^root
+       test_bisection_diff 0 $_bisect_option c2 ^root
+       test_bisection_diff 0 $_bisect_option c3 ^root
+       test_bisection_diff 0 $_bisect_option E ^F
+       test_bisection_diff 0 $_bisect_option e1 ^F
+       test_bisection_diff 0 $_bisect_option e2 ^F
+       test_bisection_diff 0 $_bisect_option e3 ^F
+       test_bisection_diff 0 $_bisect_option e4 ^F
+       test_bisection_diff 0 $_bisect_option e5 ^F
+       test_bisection_diff 0 $_bisect_option e6 ^F
+       test_bisection_diff 0 $_bisect_option e7 ^F
+       test_bisection_diff 0 $_bisect_option f1 ^F
+       test_bisection_diff 0 $_bisect_option f2 ^F
+       test_bisection_diff 0 $_bisect_option f3 ^F
+       test_bisection_diff 0 $_bisect_option f4 ^F
+       test_bisection_diff 0 $_bisect_option E ^F
+
+       test_bisection_diff 1 $_bisect_option V ^U
+       test_bisection_diff 0 $_bisect_option V ^U ^u1 ^u2 ^u3
+       test_bisection_diff 0 $_bisect_option u1 ^U
+       test_bisection_diff 0 $_bisect_option u2 ^U
+       test_bisection_diff 0 $_bisect_option u3 ^U
+       test_bisection_diff 0 $_bisect_option u4 ^U
+       test_bisection_diff 0 $_bisect_option u5 ^U
+       
+#
+# the following illustrate's Linus' binary bug blatt idea. 
+#
+# assume the bug is actually at l3, but you don't know that - all you know is that l3 is broken
+# and it wasn't broken before
+#
+# keep bisecting the list, advancing the "bad" head and accumulating "good" heads until
+# the bisection point is the head - this is the bad point.
+#
+
+test_output_expect_success "--bisect l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git-rev-list $_bisect_option l5 ^root ^c3' <<EOF
+b4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
+a4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git-rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
+l3
+EOF
+
+#
+# if l3 is bad, then l4 is bad too - so advance the bad pointer by making b4 the known bad head
+#
+
+test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+# found!
+
+#
+# as another example, let's consider a4 to be the bad head, in which case
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
+a4
+EOF
+
+# found!
+
+#
+# or consider c3 to be the bad head
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+# found!
+
+}
+
+test_sequence "--bisect"
+
+#
+#
+test_done
diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh
new file mode 100755 (executable)
index 0000000..d99a9ad
--- /dev/null
@@ -0,0 +1,408 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --topo-order functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+list_duplicates()
+{
+    "$@" | sort | uniq -d
+}
+
+date >path0
+git-update-index --add path0
+save_tag tree git-write-tree
+on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
+on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
+on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
+on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
+on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
+on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
+on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
+on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
+on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
+on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b3 tree -p b2
+on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
+on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
+on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
+on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
+on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
+on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
+on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
+on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
+on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
+on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
+on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
+on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
+on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
+on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
+on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
+on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
+
+
+#
+# note: as of 20/6, it isn't possible to create duplicate parents, so this
+# can't be tested.
+#
+#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
+hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
+save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
+save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
+save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
+save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
+save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
+save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
+save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
+save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
+save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
+save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
+save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
+save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
+save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
+save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
+save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
+
+hide_error save_tag g0 unique_commit g0 tree
+save_tag g1 unique_commit g1 tree -p g0
+save_tag h1 unique_commit g2 tree -p g0
+save_tag g2 unique_commit g3 tree -p g1 -p h1
+save_tag h2 unique_commit g4 tree -p g2
+save_tag g3 unique_commit g5 tree -p g2
+save_tag g4 unique_commit g6 tree -p g3 -p h2
+
+git-update-ref HEAD $(tag l5)
+
+test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
+19
+EOF
+
+test_output_expect_success 'simple topo order' 'git-rev-list --topo-order  HEAD' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'two diamonds topo order (g6)' 'git-rev-list --topo-order  g4' <<EOF
+g4
+h2
+g3
+g2
+h1
+g1
+g0
+EOF
+
+test_output_expect_success 'multiple heads' 'git-rev-list --topo-order a3 b3 c3' <<EOF
+a3
+a2
+a1
+c3
+c2
+c1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --topo-order a3 b3 c3 ^a1' <<EOF
+a3
+a2
+c3
+c2
+c1
+b3
+b2
+b1
+EOF
+
+test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --topo-order a3 b3 c3 ^l1' <<EOF
+a3
+a2
+a1
+c3
+c2
+c1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --topo-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'duplicated head arguments' 'git-rev-list --topo-order l5 l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'prune near topo' 'git-rev-list --topo-order a4 ^c3' <<EOF
+a4
+b4
+a3
+a2
+a1
+b3
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --topo-order  root' <<EOF
+root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --topo-order  l0' <<EOF
+l0
+root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --topo-order  l1' <<EOF
+l1
+l0
+root
+EOF
+
+test_output_expect_success "linear prune l2 ^root" 'git-rev-list --topo-order  l2 ^root' <<EOF
+l2
+l1
+l0
+EOF
+
+test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --topo-order  l2 ^l0' <<EOF
+l2
+l1
+EOF
+
+test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --topo-order  l2 ^l1' <<EOF
+l2
+EOF
+
+test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --topo-order  l5 ^a4' <<EOF
+l5
+l4
+l3
+EOF
+
+test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --topo-order  l5 ^l3' <<EOF
+l5
+l4
+EOF
+
+test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --topo-order  l5 ^l4' <<EOF
+l5
+EOF
+
+test_output_expect_success "max-count 10 - topo order" 'git-rev-list --topo-order  --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+EOF
+
+test_output_expect_success "max-count 10 - non topo order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+EOF
+
+test_output_expect_success '--max-age=c3, no --topo-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+EOF
+
+#
+# this test fails on --topo-order - a fix is required
+#
+#test_output_expect_success '--max-age=c3, --topo-order' "git-rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
+#l5
+#l4
+#l3
+#a4
+#c3
+#b4
+#a3
+#a2
+#EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git-rev-list --topo-order a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git-rev-list --topo-order c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git-rev-list a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git-rev-list c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+EOF
+
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+EOF
+
+test_expect_success "head ^head --topo-order" 'git-rev-list --topo-order  a3 ^a3' <<EOF
+EOF
+
+test_expect_success "head ^head no --topo-order" 'git-rev-list a3 ^a3' <<EOF
+EOF
+
+test_output_expect_success 'simple topo order (l5r1)' 'git-rev-list --topo-order  l5r1' <<EOF
+l5r1
+r1
+r0
+alt_root
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'simple topo order (r1l5)' 'git-rev-list --topo-order  r1l5' <<EOF
+r1l5
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+r1
+r0
+alt_root
+EOF
+
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --topo-order" <<EOF
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "--topo-order a4 l3" "git-rev-list --topo-order a4 l3" <<EOF
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+#
+#
+
+test_done
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
new file mode 100755 (executable)
index 0000000..1dce123
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Merge base computation.
+'
+
+. ./test-lib.sh
+
+T=$(git-write-tree)
+
+M=1130000000
+Z=+0000
+
+export GIT_COMMITTER_EMAIL=git@comm.iter.xz
+export GIT_COMMITTER_NAME='C O Mmiter'
+export GIT_AUTHOR_NAME='A U Thor'
+export GIT_AUTHOR_EMAIL=git@au.thor.xz
+
+doit() {
+       OFFSET=$1; shift
+       NAME=$1; shift
+       PARENTS=
+       for P
+       do
+               PARENTS="${PARENTS}-p $P "
+       done
+       GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
+       GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+       commit=$(echo $NAME | git-commit-tree $T $PARENTS)
+       echo $commit >.git/refs/tags/$NAME
+       echo $commit
+}
+
+# Setup...
+E=$(doit 5 E)
+D=$(doit 4 D $E)
+F=$(doit 6 F $E)
+C=$(doit 3 C $D)
+B=$(doit 2 B $C)
+A=$(doit 1 A $B)
+G=$(doit 7 G $B $E)
+H=$(doit 8 H $A $F)
+
+test_expect_success 'compute merge-base (single)' \
+    'MB=$(git-merge-base G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
+test_expect_success 'compute merge-base (all)' \
+    'MB=$(git-merge-base --all G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
+test_expect_success 'compute merge-base with show-branch' \
+    'MB=$(git-show-branch --merge-base G H) &&
+     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+
+test_done
diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh
new file mode 100755 (executable)
index 0000000..a19d49d
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Fredrik Kuivinen
+#
+
+test_description='Test merge with directory/file conflicts'
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' \
+'echo "Hello" > init &&
+git add init &&
+git commit -m "Initial commit" &&
+git branch B &&
+mkdir dir &&
+echo "foo" > dir/foo &&
+git add dir/foo &&
+git commit -m "File: dir/foo" &&
+git checkout B &&
+echo "file dir" > dir &&
+git add dir &&
+git commit -m "File: dir"'
+
+test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master'
+
+test_done
diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh
new file mode 100755 (executable)
index 0000000..2623813
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Fredrik Kuivinen
+#
+
+# See http://marc.theaimsgroup.com/?l=git&m=111463358500362&w=2 for a
+# nice decription of what this is about.
+
+
+test_description='Test criss-cross merge'
+. ./test-lib.sh
+
+if test "$no_python"; then
+       echo "Skipping: no python => no recursive merge"
+       test_done
+       exit 0
+fi
+
+test_expect_success 'prepare repository' \
+'echo "1
+2
+3
+4
+5
+6
+7
+8
+9" > file &&
+git add file && 
+git commit -m "Initial commit" file &&
+git branch A &&
+git branch B &&
+git checkout A &&
+echo "1
+2
+3
+4
+5
+6
+7
+8 changed in B8, branch A
+9" > file &&
+git commit -m "B8" file &&
+git checkout B &&
+echo "1
+2
+3 changed in C3, branch B
+4
+5
+6
+7
+8
+9
+" > file &&
+git commit -m "C3" file &&
+git branch C3 &&
+git merge "pre E3 merge" B A &&
+echo "1
+2
+3 changed in E3, branch B. New file size
+4
+5
+6
+7
+8 changed in B8, branch A
+9
+" > file &&
+git commit -m "E3" file &&
+git checkout A &&
+git merge "pre D8 merge" A C3 &&
+echo "1
+2
+3 changed in C3, branch B
+4
+5
+6
+7
+8 changed in D8, branch A. New file size 2
+9" > file &&
+git commit -m D8 file'
+
+test_expect_success 'Criss-cross merge' 'git merge "final merge" A B'
+
+cat > file-expect <<EOF
+1
+2
+3 changed in E3, branch B. New file size
+4
+5
+6
+7
+8 changed in D8, branch A. New file size 2
+9
+EOF
+
+test_expect_success 'Criss-cross merge result' 'cmp file file-expect'
+
+test_done
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
new file mode 100755 (executable)
index 0000000..5ac2564
--- /dev/null
@@ -0,0 +1,208 @@
+#!/bin/sh
+
+test_description='Merge-recursive merging renames'
+. ./test-lib.sh
+
+if test "$no_python"; then
+       echo "Skipping: no python => no recursive merge"
+       test_done
+       exit 0
+fi
+
+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 &&
+git branch white &&
+git branch red &&
+git branch blue &&
+
+sed -e "/^g /s/.*/g : master changes a line/" <A >A+ &&
+mv A+ A &&
+git commit -a -m "master updates A" &&
+
+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 &&
+sed -e "/^g /s/.*/g : red 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 "red renames A->B, M->N" &&
+
+git checkout blue &&
+sed -e "/^g /s/.*/g : blue changes a line/" <A >C &&
+sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+rm -f A M &&
+git update-index --add --remove A C M N &&
+git commit -m "blue renames A->C, M->N" &&
+
+git checkout master'
+
+test_expect_success 'pull renaming branch into unrenaming one' \
+'
+       git show-branch
+       git pull . white && {
+               echo "BAD: should have conflicted"
+               exit 1
+       }
+       git ls-files -s
+       test "$(git ls-files -u B | wc -l)" -eq 3 || {
+               echo "BAD: should have left stages for B"
+               exit 1  
+       }
+       test "$(git ls-files -s N | wc -l)" -eq 1 || {
+               echo "BAD: should have merged N"
+               exit 1  
+       }
+       sed -ne "/^g/{
+       p
+       q
+       }" B | grep master || {
+               echo "BAD: should have listed our change first"
+               exit 1
+       }
+       test "$(git diff white N | wc -l)" -eq 0 || {
+               echo "BAD: should have taken colored branch"
+               exit 1
+       }
+'
+
+test_expect_success 'pull renaming branch into another renaming one' \
+'
+       rm -f B
+       git reset --hard
+       git checkout red
+       git pull . white && {
+               echo "BAD: should have conflicted"
+               exit 1
+       }
+       test "$(git ls-files -u B | wc -l)" -eq 3 || {
+               echo "BAD: should have left stages"
+               exit 1  
+       }
+       test "$(git ls-files -s N | wc -l)" -eq 1 || {
+               echo "BAD: should have merged N"
+               exit 1  
+       }
+       sed -ne "/^g/{
+       p
+       q
+       }" B | grep red || {
+               echo "BAD: should have listed our change first"
+               exit 1
+       }
+       test "$(git diff white N | wc -l)" -eq 0 || {
+               echo "BAD: should have taken colored branch"
+               exit 1
+       }
+'
+
+test_expect_success 'pull unrenaming branch into renaming one' \
+'
+       git reset --hard
+       git show-branch
+       git pull . master && {
+               echo "BAD: should have conflicted"
+               exit 1
+       }
+       test "$(git ls-files -u B | wc -l)" -eq 3 || {
+               echo "BAD: should have left stages"
+               exit 1  
+       }
+       test "$(git ls-files -s N | wc -l)" -eq 1 || {
+               echo "BAD: should have merged N"
+               exit 1  
+       }
+       sed -ne "/^g/{
+       p
+       q
+       }" B | grep red || {
+               echo "BAD: should have listed our change first"
+               exit 1
+       }
+       test "$(git diff white N | wc -l)" -eq 0 || {
+               echo "BAD: should have taken colored branch"
+               exit 1
+       }
+'
+
+test_expect_success 'pull conflicting renames' \
+'
+       git reset --hard
+       git show-branch
+       git pull . blue && {
+               echo "BAD: should have conflicted"
+               exit 1
+       }
+       test "$(git ls-files -u A | wc -l)" -eq 1 || {
+               echo "BAD: should have left a stage"
+               exit 1  
+       }
+       test "$(git ls-files -u B | wc -l)" -eq 1 || {
+               echo "BAD: should have left a stage"
+               exit 1  
+       }
+       test "$(git ls-files -u C | wc -l)" -eq 1 || {
+               echo "BAD: should have left a stage"
+               exit 1  
+       }
+       test "$(git ls-files -s N | wc -l)" -eq 1 || {
+               echo "BAD: should have merged N"
+               exit 1  
+       }
+       sed -ne "/^g/{
+       p
+       q
+       }" B | grep red || {
+               echo "BAD: should have listed our change first"
+               exit 1
+       }
+       test "$(git diff white N | wc -l)" -eq 0 || {
+               echo "BAD: should have taken colored branch"
+               exit 1
+       }
+'
+
+test_done
diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh
new file mode 100755 (executable)
index 0000000..7d354a1
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+date >path0
+git-update-index --add path0
+save_tag tree git-write-tree
+hide_error save_tag start unique_commit "start" tree
+save_tag second unique_commit "second" tree -p start
+hide_error save_tag start2 unique_commit "start2" tree
+save_tag two_parents unique_commit "next" tree -p second -p start2
+save_tag final unique_commit "final" tree -p two_parents
+
+test_expect_success 'start is valid' 'git-rev-parse start | grep "^[0-9a-f]\{40\}$"'
+test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git-rev-parse start^0)"
+test_expect_success 'start^1 not valid' "if git-rev-parse --verify start^1; then false; else :; fi"
+test_expect_success 'second^1 = second^' "test $(git-rev-parse second^1) = $(git-rev-parse second^)"
+test_expect_success 'final^1^1^1' "test $(git-rev-parse start) = $(git-rev-parse final^1^1^1)"
+test_expect_success 'final^1^1^1 = final^^^' "test $(git-rev-parse final^1^1^1) = $(git-rev-parse final^^^)"
+test_expect_success 'final^1^2' "test $(git-rev-parse start2) = $(git-rev-parse final^1^2)"
+test_expect_success 'final^1^2 != final^1^1' "test $(git-rev-parse final^1^2) != $(git-rev-parse final^1^1)"
+test_expect_success 'final^1^3 not valid' "if git-rev-parse --verify final^1^3; then false; else :; fi"
+test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1'
+test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0'
+
+test_done
+
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
new file mode 100755 (executable)
index 0000000..811a479
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='git-mv in subdirs'
+. ./test-lib.sh
+
+test_expect_success \
+    'prepare reference tree' \
+    'mkdir path0 path1 &&
+     cp ../../COPYING path0/COPYING &&
+     git-add path0/COPYING &&
+     git-commit -m add -a'
+
+test_expect_success \
+    'moving the file out of subdirectory' \
+    'cd path0 && git-mv COPYING ../path1/COPYING'
+
+# in path0 currently
+test_expect_success \
+    'commiting the change' \
+    'cd .. && git-commit -m move-out -a'
+
+test_expect_success \
+    'checking the commit' \
+    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    grep -E "^R100.+path0/COPYING.+path1/COPYING"'
+
+test_expect_success \
+    'moving the file back into subdirectory' \
+    'cd path0 && git-mv ../path1/COPYING COPYING'
+
+# in path0 currently
+test_expect_success \
+    'commiting the change' \
+    'cd .. && git-commit -m move-in -a'
+
+test_expect_success \
+    'checking the commit' \
+    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    grep -E "^R100.+path1/COPYING.+path0/COPYING"'
+
+test_done
diff --git a/t/t7101-reset.sh b/t/t7101-reset.sh
new file mode 100755 (executable)
index 0000000..a919140
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='git-reset should cull empty subdirs'
+. ./test-lib.sh
+
+test_expect_success \
+    'creating initial files' \
+    'mkdir path0 &&
+     cp ../../COPYING path0/COPYING &&
+     git-add path0/COPYING &&
+     git-commit -m add -a'
+
+test_expect_success \
+    'creating second files' \
+    'mkdir path1 &&
+     mkdir path1/path2 &&
+     cp ../../COPYING path1/path2/COPYING &&
+     cp ../../COPYING path1/COPYING &&
+     cp ../../COPYING COPYING &&
+     cp ../../COPYING path0/COPYING-TOO &&
+     git-add path1/path2/COPYING &&
+     git-add path1/COPYING &&
+     git-add COPYING &&
+     git-add path0/COPYING-TOO &&
+     git-commit -m change -a'
+
+test_expect_success \
+    'resetting tree HEAD^' \
+    'git-reset --hard HEAD^'
+
+test_expect_success \
+    'checking initial files exist after rewind' \
+    'test -d path0 &&
+     test -f path0/COPYING'
+
+test_expect_failure \
+    'checking lack of path1/path2/COPYING' \
+    'test -f path1/path2/COPYING'
+
+test_expect_failure \
+    'checking lack of path1/COPYING' \
+    'test -f path1/COPYING'
+
+test_expect_failure \
+    'checking lack of COPYING' \
+    'test -f COPYING'
+
+test_expect_failure \
+    'checking checking lack of path1/COPYING-TOO' \
+    'test -f path0/COPYING-TOO'
+
+test_expect_failure \
+    'checking lack of path1/path2' \
+    'test -d path1/path2'
+
+test_expect_failure \
+    'checking lack of path1' \
+    'test -d path1'
+
+test_done
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
new file mode 100755 (executable)
index 0000000..2496397
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='git-annotate'
+. ./test-lib.sh
+
+PROG='git annotate'
+. ../annotate-tests.sh
+
+test_done
diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh
new file mode 100755 (executable)
index 0000000..9777393
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+test_description='git-blame'
+. ./test-lib.sh
+
+PROG='git blame -c'
+. ../annotate-tests.sh
+
+test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
new file mode 100755 (executable)
index 0000000..a61da1e
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+test_description='git-send-email'
+. ./test-lib.sh
+
+PROG='git send-email'
+test_expect_success \
+    'prepare reference tree' \
+    'echo "1A quick brown fox jumps over the" >file &&
+     echo "lazy dog" >>file &&
+     git add file
+     GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+    'Setup helper tool' \
+    '(echo "#!/bin/sh"
+      echo shift
+      echo for a
+      echo do
+      echo "  echo \"!\$a!\""
+      echo "done >commandline"
+      echo "cat > msgtxt"
+      ) >fake.sendmail
+     chmod +x ./fake.sendmail
+     git add fake.sendmail
+     GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
+
+test_expect_success \
+    'Extract patches and send' \
+    'git format-patch -n HEAD^1
+     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" ./0001*txt'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!author@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'diff commandline expected'
+
+test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
new file mode 100755 (executable)
index 0000000..05f6e79
--- /dev/null
@@ -0,0 +1,217 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+# For repeatability, reset the environment to known value.
+LANG=C
+LC_ALL=C
+PAGER=cat
+TZ=UTC
+export LANG LC_ALL PAGER TZ
+unset AUTHOR_DATE
+unset AUTHOR_EMAIL
+unset AUTHOR_NAME
+unset COMMIT_AUTHOR_EMAIL
+unset COMMIT_AUTHOR_NAME
+unset GIT_ALTERNATE_OBJECT_DIRECTORIES
+unset GIT_AUTHOR_DATE
+GIT_AUTHOR_EMAIL=author@example.com
+GIT_AUTHOR_NAME='A U Thor'
+unset GIT_COMMITTER_DATE
+GIT_COMMITTER_EMAIL=committer@example.com
+GIT_COMMITTER_NAME='C O Mitter'
+unset GIT_DIFF_OPTS
+unset GIT_DIR
+unset GIT_EXTERNAL_DIFF
+unset GIT_INDEX_FILE
+unset GIT_OBJECT_DIRECTORY
+unset SHA1_FILE_DIRECTORIES
+unset SHA1_FILE_DIRECTORY
+export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
+export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
+
+# Each test should start with something like this, after copyright notices:
+#
+# test_description='Description of this test...
+# This test checks if command xyzzy does the right thing...
+# '
+# . ./test-lib.sh
+
+error () {
+       echo "* error: $*"
+       trap - exit
+       exit 1
+}
+
+say () {
+       echo "* $*"
+}
+
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+while test "$#" -ne 0
+do
+       case "$1" in
+       -d|--d|--de|--deb|--debu|--debug)
+               debug=t; shift ;;
+       -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
+               immediate=t; shift ;;
+       -h|--h|--he|--hel|--help)
+               echo "$test_description"
+               exit 0 ;;
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t; shift ;;
+       --no-python)
+               no_python=t; shift ;;
+       *)
+               break ;;
+       esac
+done
+
+exec 5>&1
+if test "$verbose" = "t"
+then
+       exec 4>&2 3>&1
+else
+       exec 4>/dev/null 3>/dev/null
+fi
+
+test_failure=0
+test_count=0
+
+trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit
+
+
+# You are not expected to call test_ok_ and test_failure_ directly, use
+# the text_expect_* functions instead.
+
+test_ok_ () {
+       test_count=$(expr "$test_count" + 1)
+       say "  ok $test_count: $@"
+}
+
+test_failure_ () {
+       test_count=$(expr "$test_count" + 1)
+       test_failure=$(expr "$test_failure" + 1);
+       say "FAIL $test_count: $1"
+       shift
+       echo "$@" | sed -e 's/^/        /'
+       test "$immediate" = "" || { trap - exit; exit 1; }
+}
+
+
+test_debug () {
+       test "$debug" = "" || eval "$1"
+}
+
+test_run_ () {
+       eval >&3 2>&4 "$1"
+       eval_ret="$?"
+       return 0
+}
+
+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 ]
+       then
+               test_ok_ "$1"
+       else
+               test_failure_ "$@"
+       fi
+}
+
+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 ]
+       then
+               test_ok_ "$1"
+       else
+               test_failure_ "$@"
+       fi
+}
+
+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" ]
+       then
+               test_ok_ "$2"
+       else
+               test_failure_ "$@"
+       fi
+}
+
+# Most tests can use the created repository, but some amy need to create more.
+# Usage: test_create_repo <directory>
+test_create_repo () {
+       test "$#" = 1 ||
+       error "bug in the test script: not 1 parameter to test-create-repo"
+       owd=`pwd`
+       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?"
+       mv .git/hooks .git/hooks-disabled
+       cd "$owd"
+}
+       
+test_done () {
+       trap - exit
+       case "$test_failure" in
+       0)
+               # We could:
+               # cd .. && rm -fr trash
+               # but that means we forbid any tests that use their own
+               # subdirectory from calling test_done without coming back
+               # to where they started from.
+               # The Makefile provided will clean this test area so
+               # we will leave things as they are.
+
+               say "passed all $test_count test(s)"
+               exit 0 ;;
+
+       *)
+               say "failed $test_failure among $test_count test(s)"
+               exit 1 ;;
+
+       esac
+}
+
+# Test the binaries we have just built.  The tests are kept in
+# t/ subdirectory and are run in trash subdirectory.
+PATH=$(pwd)/..:$PATH
+GIT_EXEC_PATH=$(pwd)/..
+export PATH GIT_EXEC_PATH
+
+# 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` || {
+       error "You haven't built things yet, have you?"
+}
+"$PYTHON" -c 'import subprocess' 2>/dev/null || {
+       PYTHONPATH=$(pwd)/../compat
+       export PYTHONPATH
+}
+test -d ../templates/blt || {
+       error "You haven't built things yet, have you?"
+}
+
+# Test repository
+test=trash
+rm -fr "$test"
+test_create_repo $test
+cd "$test"
diff --git a/t/test4012.png b/t/test4012.png
new file mode 100644 (file)
index 0000000..7b181d1
Binary files /dev/null and b/t/test4012.png differ
diff --git a/tag.c b/tag.c
new file mode 100644 (file)
index 0000000..f390ee7
--- /dev/null
+++ b/tag.c
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include "tag.h"
+
+const char *tag_type = "tag";
+
+struct object *deref_tag(struct object *o, const char *warn, int warnlen)
+{
+       while (o && o->type == tag_type)
+               o = parse_object(((struct tag *)o)->tagged->sha1);
+       if (!o && warn) {
+               if (!warnlen)
+                       warnlen = strlen(warn);
+               error("missing object referenced by '%.*s'", warnlen, warn);
+       }
+       return o;
+}
+
+struct tag *lookup_tag(const unsigned char *sha1)
+{
+        struct object *obj = lookup_object(sha1);
+        if (!obj) {
+                struct tag *ret = xcalloc(1, sizeof(struct tag));
+                created_object(sha1, &ret->object);
+                ret->object.type = tag_type;
+                return ret;
+        }
+       if (!obj->type)
+               obj->type = tag_type;
+        if (obj->type != tag_type) {
+                error("Object %s is a %s, not a tree", 
+                      sha1_to_hex(sha1), obj->type);
+                return NULL;
+        }
+        return (struct tag *) obj;
+}
+
+int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
+{
+       int typelen, taglen;
+       unsigned char object[20];
+       const char *type_line, *tag_line, *sig_line;
+       char type[20];
+
+        if (item->object.parsed)
+                return 0;
+        item->object.parsed = 1;
+
+       if (size < 64)
+               return -1;
+       if (memcmp("object ", data, 7) || get_sha1_hex(data + 7, object))
+               return -1;
+
+       type_line = data + 48;
+       if (memcmp("\ntype ", type_line-1, 6))
+               return -1;
+
+       tag_line = strchr(type_line, '\n');
+       if (!tag_line || memcmp("tag ", ++tag_line, 4))
+               return -1;
+
+       sig_line = strchr(tag_line, '\n');
+       if (!sig_line)
+               return -1;
+       sig_line++;
+
+       typelen = tag_line - type_line - strlen("type \n");
+       if (typelen >= 20)
+               return -1;
+       memcpy(type, type_line + 5, typelen);
+       type[typelen] = '\0';
+       taglen = sig_line - tag_line - strlen("tag \n");
+       item->tag = xmalloc(taglen + 1);
+       memcpy(item->tag, tag_line + 4, taglen);
+       item->tag[taglen] = '\0';
+
+       item->tagged = lookup_object_type(object, type);
+       if (item->tagged && track_object_refs) {
+               struct object_refs *refs = alloc_object_refs(1);
+               refs->ref[0] = item->tagged;
+               set_object_refs(&item->object, refs);
+       }
+
+       return 0;
+}
+
+int parse_tag(struct tag *item)
+{
+       char type[20];
+       void *data;
+       unsigned long size;
+       int ret;
+
+       if (item->object.parsed)
+               return 0;
+       data = read_sha1_file(item->object.sha1, type, &size);
+       if (!data)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, tag_type)) {
+               free(data);
+               return error("Object %s not a tag",
+                            sha1_to_hex(item->object.sha1));
+       }
+       ret = parse_tag_buffer(item, data, size);
+       free(data);
+       return ret;
+}
diff --git a/tag.h b/tag.h
new file mode 100644 (file)
index 0000000..7a0cb00
--- /dev/null
+++ b/tag.h
@@ -0,0 +1,20 @@
+#ifndef TAG_H
+#define TAG_H
+
+#include "object.h"
+
+extern const char *tag_type;
+
+struct tag {
+       struct object object;
+       struct object *tagged;
+       char *tag;
+       char *signature; /* not actually implemented */
+};
+
+extern struct tag *lookup_tag(const unsigned char *sha1);
+extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
+extern int parse_tag(struct tag *item);
+extern struct object *deref_tag(struct object *, const char *, int);
+
+#endif /* TAG_H */
diff --git a/tar.h b/tar.h
new file mode 100644 (file)
index 0000000..3467705
--- /dev/null
+++ b/tar.h
@@ -0,0 +1,25 @@
+#define TYPEFLAG_AUTO          '\0'
+#define TYPEFLAG_REG           '0'
+#define TYPEFLAG_LNK           '2'
+#define TYPEFLAG_DIR           '5'
+#define TYPEFLAG_GLOBAL_HEADER 'g'
+#define TYPEFLAG_EXT_HEADER    'x'
+
+struct ustar_header {
+       char name[100];         /*   0 */
+       char mode[8];           /* 100 */
+       char uid[8];            /* 108 */
+       char gid[8];            /* 116 */
+       char size[12];          /* 124 */
+       char mtime[12];         /* 136 */
+       char chksum[8];         /* 148 */
+       char typeflag[1];       /* 156 */
+       char linkname[100];     /* 157 */
+       char magic[6];          /* 257 */
+       char version[2];        /* 263 */
+       char uname[32];         /* 265 */
+       char gname[32];         /* 297 */
+       char devmajor[8];       /* 329 */
+       char devminor[8];       /* 337 */
+       char prefix[155];       /* 345 */
+};
diff --git a/templates/.gitignore b/templates/.gitignore
new file mode 100644 (file)
index 0000000..6759ecb
--- /dev/null
@@ -0,0 +1,2 @@
+blt
+boilerplates.made
diff --git a/templates/Makefile b/templates/Makefile
new file mode 100644 (file)
index 0000000..8f7f4fe
--- /dev/null
@@ -0,0 +1,48 @@
+# make and install sample templates
+
+INSTALL ?= install
+TAR ?= tar
+prefix ?= $(HOME)
+template_dir ?= $(prefix)/share/git-core/templates/
+# DESTDIR=
+
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+all: boilerplates.made custom
+
+# Put templates that can be copied straight from the source
+# in a file direc--tory--file in the source.  They will be
+# just copied to the destination.
+
+bpsrc = $(filter-out %~,$(wildcard *--*))
+boilerplates.made : $(bpsrc)
+       ls *--* 2>/dev/null | \
+       while read boilerplate; \
+       do \
+               case "$$boilerplate" in *~) continue ;; esac && \
+               dst=`echo "$$boilerplate" | sed -e 's|^this|.|;s|--|/|g'` && \
+               dir=`expr "$$dst" : '\(.*\)/'` && \
+               mkdir -p blt/$$dir && \
+               case "$$boilerplate" in \
+               *--) ;; \
+               *) cp $$boilerplate blt/$$dst ;; \
+               esac || exit; \
+       done || exit
+       date >$@
+
+# If you need build-tailored templates, build them into blt/
+# directory yourself here.
+custom:
+       : no custom templates yet
+
+clean:
+       rm -rf blt boilerplates.made
+
+install: all
+       $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(template_dir))
+       (cd blt && $(TAR) cf - .) | \
+       (cd $(call shellquote,$(DESTDIR)$(template_dir)) && $(TAR) xf -)
diff --git a/templates/branches-- b/templates/branches--
new file mode 100644 (file)
index 0000000..fae8870
--- /dev/null
@@ -0,0 +1 @@
+: this is just to ensure the directory exists.
diff --git a/templates/hooks--applypatch-msg b/templates/hooks--applypatch-msg
new file mode 100644 (file)
index 0000000..02de1ef
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.  The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+       exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg
new file mode 100644 (file)
index 0000000..643822d
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by git-commit with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+        sort | uniq -c | sed -e '/^[   ]*1 /d')"
diff --git a/templates/hooks--post-commit b/templates/hooks--post-commit
new file mode 100644 (file)
index 0000000..8be6f34
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, make this file executable.
+
+: Nothing
diff --git a/templates/hooks--post-update b/templates/hooks--post-update
new file mode 100644 (file)
index 0000000..bcba893
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, make this file executable by "chmod +x post-update".
+
+exec git-update-server-info
diff --git a/templates/hooks--pre-applypatch b/templates/hooks--pre-applypatch
new file mode 100644 (file)
index 0000000..5f56ce8
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+       exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
+
diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit
new file mode 100644 (file)
index 0000000..723a9ef
--- /dev/null
@@ -0,0 +1,71 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by git-commit with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+# This is slightly modified from Andrew Morton's Perfect Patch.
+# Lines you introduce should not have trailing whitespace.
+# Also check for an indentation that has SP before a TAB.
+
+if git-rev-parse --verify HEAD 2>/dev/null
+then
+       git-diff-index -p -M --cached HEAD
+else
+       # NEEDSWORK: we should produce a diff with an empty tree here
+       # if we want to do the same verification for the initial import.
+       :
+fi |
+perl -e '
+    my $found_bad = 0;
+    my $filename;
+    my $reported_filename = "";
+    my $lineno;
+    sub bad_line {
+       my ($why, $line) = @_;
+       if (!$found_bad) {
+           print STDERR "*\n";
+           print STDERR "* You have some suspicious patch lines:\n";
+           print STDERR "*\n";
+           $found_bad = 1;
+       }
+       if ($reported_filename ne $filename) {
+           print STDERR "* In $filename\n";
+           $reported_filename = $filename;
+       }
+       print STDERR "* $why (line $lineno)\n";
+       print STDERR "$filename:$lineno:$line\n";
+    }
+    while (<>) {
+       if (m|^diff --git a/(.*) b/\1$|) {
+           $filename = $1;
+           next;
+       }
+       if (/^@@ -\S+ \+(\d+)/) {
+           $lineno = $1 - 1;
+           next;
+       }
+       if (/^ /) {
+           $lineno++;
+           next;
+       }
+       if (s/^\+//) {
+           $lineno++;
+           chomp;
+           if (/\s$/) {
+               bad_line("trailing whitespace", $_);
+           }
+           if (/^\s*   /) {
+               bad_line("indent SP followed by a TAB", $_);
+           }
+           if (/^(?:[<>=]){7}/) {
+               bad_line("unresolved merge conflict", $_);
+           }
+       }
+    }
+    exit($found_bad);
+'
+
diff --git a/templates/hooks--pre-rebase b/templates/hooks--pre-rebase
new file mode 100644 (file)
index 0000000..981c454
--- /dev/null
@@ -0,0 +1,150 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+       topic="refs/heads/$2"
+else
+       topic=`git symbolic-ref HEAD`
+fi
+
+case "$basebranch,$topic" in
+master,refs/heads/??/*)
+       ;;
+*)
+       exit 0 ;# we do not interrupt others.
+       ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master.  Is it OK to rebase it?
+
+# Is topic fully merged to master?
+not_in_master=`git-rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+       echo >&2 "$topic is fully merged to master; better remove it."
+       exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next?  If so you should not be rebasing it.
+only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git-rev-list ^master           ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+       not_in_topic=`git-rev-list "^$topic" master`
+       if test -z "$not_in_topic"
+       then
+               echo >&2 "$topic is already up-to-date with master"
+               exit 1 ;# we could allow it, but there is no point.
+       else
+               exit 0
+       fi
+else
+       not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"`
+       perl -e '
+               my $topic = $ARGV[0];
+               my $msg = "* $topic has commits already merged to public branch:\n";
+               my (%not_in_next) = map {
+                       /^([0-9a-f]+) /;
+                       ($1 => 1);
+               } split(/\n/, $ARGV[1]);
+               for my $elem (map {
+                               /^([0-9a-f]+) (.*)$/;
+                               [$1 => $2];
+                       } split(/\n/, $ARGV[2])) {
+                       if (!exists $not_in_next{$elem->[0]}) {
+                               if ($msg) {
+                                       print STDERR $msg;
+                                       undef $msg;
+                               }
+                               print STDERR " $elem->[1]\n";
+                       }
+               }
+       ' "$topic" "$not_in_next" "$not_in_master"
+       exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+   merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+   it is deleted.  If you need to build on top of it to correct
+   earlier mistakes, a new topic branch is created by forking at
+   the tip of the "master".  This is not strictly necessary, but
+   it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+   branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next".  Young
+    topic branches can have stupid mistakes you would rather
+    clean up before publishing, and things that have not been
+    merged into other branches can be easily rebased without
+    affecting other people.  But once it is published, you would
+    not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+    Then you can delete it.  More importantly, you should not
+    build on top of it -- other people may already want to
+    change things related to the topic as patches against your
+    "master", so if you need further changes, it is better to
+    fork the topic (perhaps with the same name) afresh from the
+    tip of "master".
+
+Let's look at this example:
+
+                  o---o---o---o---o---o---o---o---o---o "next"
+                 /       /           /           /
+                /   a---a---b A     /           /
+               /   /               /           /
+              /   /   c---c---c---c B         /
+             /   /   /             \         /
+            /   /   /   b---b C     \       /
+           /   /   /   /             \     /
+    ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished.  It has been fully merged up to "master" and "next",
+   and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+       git-rev-list ^master ^topic next
+       git-rev-list ^master        next
+
+       if these match, topic has not merged in next at all.
+
+To compute (2):
+
+       git-rev-list master..topic
+
+       if this is empty, it is fully merged to "master".
diff --git a/templates/hooks--update b/templates/hooks--update
new file mode 100644 (file)
index 0000000..d7a8f0a
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# An example hook script to mail out commit update information.
+# It 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".
+#
+
+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
+
+# 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
+               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 $prev..$3 | diffstat -p1
+                       echo ---
+               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)
+               ;;
+       esac
+else
+       base=$(git-merge-base "$2" "$3")
+       case "$base" in
+       "$2")
+               git diff "$3" "^$base" | diffstat -p1
+               echo
+               echo "New commits:"
+               ;;
+       *)
+               echo "Rebased ref, commits from common ancestor:"
+               ;;
+       esac
+       git-rev-list --pretty "$3" "^$base"
+fi) |
+mail -s "$project: Changes to '${1##refs/heads/}'" $recipients
+exit 0
diff --git a/templates/info--exclude b/templates/info--exclude
new file mode 100644 (file)
index 0000000..2c87b72
--- /dev/null
@@ -0,0 +1,6 @@
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/templates/remotes-- b/templates/remotes--
new file mode 100644 (file)
index 0000000..fae8870
--- /dev/null
@@ -0,0 +1 @@
+: this is just to ensure the directory exists.
diff --git a/templates/this--description b/templates/this--description
new file mode 100644 (file)
index 0000000..c6f25e8
--- /dev/null
@@ -0,0 +1 @@
+Unnamed repository; edit this file to name it for gitweb.
diff --git a/test-date.c b/test-date.c
new file mode 100644 (file)
index 0000000..93e8027
--- /dev/null
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <time.h>
+
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               char result[100];
+               time_t t;
+
+               memcpy(result, "bad", 4);
+               parse_date(argv[i], result, sizeof(result));
+               t = strtoul(result, NULL, 0);
+               printf("%s -> %s -> %s", argv[i], result, ctime(&t));
+
+               t = approxidate(argv[i]);
+               printf("%s -> %s\n", argv[i], ctime(&t));
+       }
+       return 0;
+}
diff --git a/test-delta.c b/test-delta.c
new file mode 100644 (file)
index 0000000..1be8ee0
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * test-delta.c: test code to exercise diff-delta.c and patch-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include "delta.h"
+
+static const char usage[] =
+       "test-delta (-d|-p) <from_file> <data_file> <out_file>";
+
+int main(int argc, char *argv[])
+{
+       int fd;
+       struct stat st;
+       void *from_buf, *data_buf, *out_buf;
+       unsigned long from_size, data_size, out_size;
+
+       if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) {
+               fprintf(stderr, "Usage: %s\n", usage);
+               return 1;
+       }
+
+       fd = open(argv[2], O_RDONLY);
+       if (fd < 0 || fstat(fd, &st)) {
+               perror(argv[2]);
+               return 1;
+       }
+       from_size = st.st_size;
+       from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (from_buf == MAP_FAILED) {
+               perror(argv[2]);
+               close(fd);
+               return 1;
+       }
+       close(fd);
+
+       fd = open(argv[3], O_RDONLY);
+       if (fd < 0 || fstat(fd, &st)) {
+               perror(argv[3]);
+               return 1;
+       }
+       data_size = st.st_size;
+       data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (data_buf == MAP_FAILED) {
+               perror(argv[3]);
+               close(fd);
+               return 1;
+       }
+       close(fd);
+
+       if (argv[1][1] == 'd')
+               out_buf = diff_delta(from_buf, from_size,
+                                    data_buf, data_size,
+                                    &out_size, 0);
+       else
+               out_buf = patch_delta(from_buf, from_size,
+                                     data_buf, data_size,
+                                     &out_size);
+       if (!out_buf) {
+               fprintf(stderr, "delta operation failed (returned NULL)\n");
+               return 1;
+       }
+
+       fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
+       if (fd < 0 || write(fd, out_buf, out_size) != out_size) {
+               perror(argv[4]);
+               return 1;
+       }
+
+       return 0;
+}
diff --git a/tree-diff.c b/tree-diff.c
new file mode 100644 (file)
index 0000000..1cdf8aa
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Helper functions for tree diff generation
+ */
+#include "cache.h"
+#include "diff.h"
+#include "tree.h"
+
+static char *malloc_base(const char *base, const char *path, int pathlen)
+{
+       int baselen = strlen(base);
+       char *newbase = xmalloc(baselen + pathlen + 2);
+       memcpy(newbase, base, baselen);
+       memcpy(newbase + baselen, path, pathlen);
+       memcpy(newbase + baselen + pathlen, "/", 2);
+       return newbase;
+}
+
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base);
+
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       unsigned mode1, mode2;
+       const char *path1, *path2;
+       const unsigned char *sha1, *sha2;
+       int cmp, pathlen1, pathlen2;
+
+       sha1 = tree_entry_extract(t1, &path1, &mode1);
+       sha2 = tree_entry_extract(t2, &path2, &mode2);
+
+       pathlen1 = strlen(path1);
+       pathlen2 = strlen(path2);
+       cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
+       if (cmp < 0) {
+               show_entry(opt, "-", t1, base);
+               return -1;
+       }
+       if (cmp > 0) {
+               show_entry(opt, "+", t2, base);
+               return 1;
+       }
+       if (!opt->find_copies_harder &&
+           !memcmp(sha1, sha2, 20) && mode1 == mode2)
+               return 0;
+
+       /*
+        * If the filemode has changed to/from a directory from/to a regular
+        * file, we need to consider it a remove and an add.
+        */
+       if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
+               show_entry(opt, "-", t1, base);
+               show_entry(opt, "+", t2, base);
+               return 0;
+       }
+
+       if (opt->recursive && S_ISDIR(mode1)) {
+               int retval;
+               char *newbase = malloc_base(base, path1, pathlen1);
+               if (opt->tree_in_recursive)
+                       opt->change(opt, mode1, mode2,
+                                   sha1, sha2, base, path1);
+               retval = diff_tree_sha1(sha1, sha2, newbase, opt);
+               free(newbase);
+               return retval;
+       }
+
+       opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+       return 0;
+}
+
+static int interesting(struct tree_desc *desc, const char *base, struct diff_options *opt)
+{
+       const char *path;
+       unsigned mode;
+       int i;
+       int baselen, pathlen;
+
+       if (!opt->nr_paths)
+               return 1;
+
+       (void)tree_entry_extract(desc, &path, &mode);
+
+       pathlen = strlen(path);
+       baselen = strlen(base);
+
+       for (i=0; i < opt->nr_paths; i++) {
+               const char *match = opt->paths[i];
+               int matchlen = opt->pathlens[i];
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (strncmp(base, match, matchlen))
+                               continue;
+
+                       /* The base is a subdirectory of a path which was specified. */
+                       return 1;
+               }
+
+               /* Does the base match? */
+               if (strncmp(base, match, baselen))
+                       continue;
+
+               match += baselen;
+               matchlen -= baselen;
+
+               if (pathlen > matchlen)
+                       continue;
+
+               if (matchlen > pathlen) {
+                       if (match[pathlen] != '/')
+                               continue;
+                       if (!S_ISDIR(mode))
+                               continue;
+               }
+
+               if (strncmp(path, match, pathlen))
+                       continue;
+
+               return 1;
+       }
+       return 0; /* No matches */
+}
+
+/* A whole sub-tree went away or appeared */
+static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       while (desc->size) {
+               if (interesting(desc, base, opt))
+                       show_entry(opt, prefix, desc, base);
+               update_tree_entry(desc);
+       }
+}
+
+/* A file entry went away or appeared */
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+       unsigned mode;
+       const char *path;
+       const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
+
+       if (opt->recursive && S_ISDIR(mode)) {
+               char type[20];
+               char *newbase = malloc_base(base, path, strlen(path));
+               struct tree_desc inner;
+               void *tree;
+
+               tree = read_sha1_file(sha1, type, &inner.size);
+               if (!tree || strcmp(type, tree_type))
+                       die("corrupt tree sha %s", sha1_to_hex(sha1));
+
+               inner.buf = tree;
+               show_tree(opt, prefix, &inner, newbase);
+
+               free(tree);
+               free(newbase);
+               return 0;
+       }
+
+       opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+       return 0;
+}
+
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       while (t1->size | t2->size) {
+               if (opt->nr_paths && t1->size && !interesting(t1, base, opt)) {
+                       update_tree_entry(t1);
+                       continue;
+               }
+               if (opt->nr_paths && t2->size && !interesting(t2, base, opt)) {
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t1->size) {
+                       show_entry(opt, "+", t2, base);
+                       update_tree_entry(t2);
+                       continue;
+               }
+               if (!t2->size) {
+                       show_entry(opt, "-", t1, base);
+                       update_tree_entry(t1);
+                       continue;
+               }
+               switch (compare_tree_entry(t1, t2, base, opt)) {
+               case -1:
+                       update_tree_entry(t1);
+                       continue;
+               case 0:
+                       update_tree_entry(t1);
+                       /* Fallthrough */
+               case 1:
+                       update_tree_entry(t2);
+                       continue;
+               }
+               die("git-diff-tree: internal error");
+       }
+       return 0;
+}
+
+int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
+{
+       void *tree1, *tree2;
+       struct tree_desc t1, t2;
+       int retval;
+
+       tree1 = read_object_with_reference(old, tree_type, &t1.size, NULL);
+       if (!tree1)
+               die("unable to read source tree (%s)", sha1_to_hex(old));
+       tree2 = read_object_with_reference(new, tree_type, &t2.size, NULL);
+       if (!tree2)
+               die("unable to read destination tree (%s)", sha1_to_hex(new));
+       t1.buf = tree1;
+       t2.buf = tree2;
+       retval = diff_tree(&t1, &t2, base, opt);
+       free(tree1);
+       free(tree2);
+       return retval;
+}
+
+static int count_paths(const char **paths)
+{
+       int i = 0;
+       while (*paths++)
+               i++;
+       return i;
+}
+
+void diff_tree_release_paths(struct diff_options *opt)
+{
+       free(opt->pathlens);
+}
+
+void diff_tree_setup_paths(const char **p, struct diff_options *opt)
+{
+       opt->nr_paths = 0;
+       opt->pathlens = NULL;
+       opt->paths = NULL;
+
+       if (p) {
+               int i;
+
+               opt->paths = p;
+               opt->nr_paths = count_paths(p);
+               if (opt->nr_paths == 0) {
+                       opt->pathlens = NULL;
+                       return;
+               }
+               opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
+               for (i=0; i < opt->nr_paths; i++)
+                       opt->pathlens[i] = strlen(p[i]);
+       }
+}
diff --git a/tree-walk.c b/tree-walk.c
new file mode 100644 (file)
index 0000000..297c697
--- /dev/null
@@ -0,0 +1,211 @@
+#include "cache.h"
+#include "tree-walk.h"
+#include "tree.h"
+
+void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1)
+{
+       unsigned long size = 0;
+       void *buf = NULL;
+
+       if (sha1) {
+               buf = read_object_with_reference(sha1, tree_type, &size, NULL);
+               if (!buf)
+                       die("unable to read tree %s", sha1_to_hex(sha1));
+       }
+       desc->size = size;
+       desc->buf = buf;
+       return buf;
+}
+
+static int entry_compare(struct name_entry *a, struct name_entry *b)
+{
+       return base_name_compare(
+                       a->path, a->pathlen, a->mode,
+                       b->path, b->pathlen, b->mode);
+}
+
+static void entry_clear(struct name_entry *a)
+{
+       memset(a, 0, sizeof(*a));
+}
+
+static void entry_extract(struct tree_desc *t, struct name_entry *a)
+{
+       a->sha1 = tree_entry_extract(t, &a->path, &a->mode);
+       a->pathlen = strlen(a->path);
+}
+
+void update_tree_entry(struct tree_desc *desc)
+{
+       const void *buf = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(buf) + 1 + 20;
+
+       if (size < len)
+               die("corrupt tree file");
+       desc->buf = buf + len;
+       desc->size = size - len;
+}
+
+static const char *get_mode(const char *str, unsigned int *modep)
+{
+       unsigned char c;
+       unsigned int mode = 0;
+
+       while ((c = *str++) != ' ') {
+               if (c < '0' || c > '7')
+                       return NULL;
+               mode = (mode << 3) + (c - '0');
+       }
+       *modep = mode;
+       return str;
+}
+
+const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+{
+       const void *tree = desc->buf;
+       unsigned long size = desc->size;
+       int len = strlen(tree)+1;
+       const unsigned char *sha1 = tree + len;
+       const char *path;
+       unsigned int mode;
+
+       path = get_mode(tree, &mode);
+       if (!path || size < len + 20)
+               die("corrupt tree file");
+       *pathp = path;
+       *modep = canon_mode(mode);
+       return sha1;
+}
+
+int tree_entry(struct tree_desc *desc, struct name_entry *entry)
+{
+       const void *tree = desc->buf, *path;
+       unsigned long len, size = desc->size;
+
+       if (!size)
+               return 0;
+
+       path = get_mode(tree, &entry->mode);
+       if (!path)
+               die("corrupt tree file");
+
+       entry->path = path;
+       len = strlen(path);
+       entry->pathlen = len;
+
+       path += len + 1;
+       entry->sha1 = path;
+
+       path += 20;
+       len = path - tree;
+       if (len > size)
+               die("corrupt tree file");
+
+       desc->buf = path;
+       desc->size = size - len;
+       return 1;
+}
+
+void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback)
+{
+       struct name_entry *entry = xmalloc(n*sizeof(*entry));
+
+       for (;;) {
+               struct name_entry entry[3];
+               unsigned long mask = 0;
+               int i, last;
+
+               last = -1;
+               for (i = 0; i < n; i++) {
+                       if (!t[i].size)
+                               continue;
+                       entry_extract(t+i, entry+i);
+                       if (last >= 0) {
+                               int cmp = entry_compare(entry+i, entry+last);
+
+                               /*
+                                * Is the new name bigger than the old one?
+                                * Ignore it
+                                */
+                               if (cmp > 0)
+                                       continue;
+                               /*
+                                * Is the new name smaller than the old one?
+                                * Ignore all old ones
+                                */
+                               if (cmp < 0)
+                                       mask = 0;
+                       }
+                       mask |= 1ul << i;
+                       last = i;
+               }
+               if (!mask)
+                       break;
+
+               /*
+                * Update the tree entries we've walked, and clear
+                * all the unused name-entries.
+                */
+               for (i = 0; i < n; i++) {
+                       if (mask & (1ul << i)) {
+                               update_tree_entry(t+i);
+                               continue;
+                       }
+                       entry_clear(entry + i);
+               }
+               callback(n, mask, entry, base);
+       }
+       free(entry);
+}
+
+static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
+{
+       int namelen = strlen(name);
+       while (t->size) {
+               const char *entry;
+               const unsigned char *sha1;
+               int entrylen, cmp;
+
+               sha1 = tree_entry_extract(t, &entry, mode);
+               update_tree_entry(t);
+               entrylen = strlen(entry);
+               if (entrylen > namelen)
+                       continue;
+               cmp = memcmp(name, entry, entrylen);
+               if (cmp > 0)
+                       continue;
+               if (cmp < 0)
+                       break;
+               if (entrylen == namelen) {
+                       memcpy(result, sha1, 20);
+                       return 0;
+               }
+               if (name[entrylen] != '/')
+                       continue;
+               if (!S_ISDIR(*mode))
+                       break;
+               if (++entrylen == namelen) {
+                       memcpy(result, sha1, 20);
+                       return 0;
+               }
+               return get_tree_entry(sha1, name + entrylen, result, mode);
+       }
+       return -1;
+}
+
+int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode)
+{
+       int retval;
+       void *tree;
+       struct tree_desc t;
+
+       tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL);
+       if (!tree)
+               return -1;
+       t.buf = tree;
+       retval = find_tree_entry(&t, name, sha1, mode);
+       free(tree);
+       return retval;
+}
+
diff --git a/tree-walk.h b/tree-walk.h
new file mode 100644 (file)
index 0000000..e57befa
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef TREE_WALK_H
+#define TREE_WALK_H
+
+struct tree_desc {
+       const void *buf;
+       unsigned long size;
+};
+
+struct name_entry {
+       const unsigned char *sha1;
+       const char *path;
+       unsigned int mode;
+       int pathlen;
+};
+
+void update_tree_entry(struct tree_desc *);
+const unsigned char *tree_entry_extract(struct tree_desc *, const char **, unsigned int *);
+
+/* Helper function that does both of the above and returns true for success */
+int tree_entry(struct tree_desc *, struct name_entry *);
+
+void *fill_tree_descriptor(struct tree_desc *desc, const unsigned char *sha1);
+
+typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry *entry, const char *base);
+
+void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback);
+
+int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
+
+#endif
diff --git a/tree.c b/tree.c
new file mode 100644 (file)
index 0000000..9bbe2da
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,230 @@
+#include "cache.h"
+#include "tree.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree-walk.h"
+#include <stdlib.h>
+
+const char *tree_type = "tree";
+
+static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+{
+       int len;
+       unsigned int size;
+       struct cache_entry *ce;
+
+       if (S_ISDIR(mode))
+               return READ_TREE_RECURSIVE;
+
+       len = strlen(pathname);
+       size = cache_entry_size(baselen + len);
+       ce = xcalloc(1, size);
+
+       ce->ce_mode = create_ce_mode(mode);
+       ce->ce_flags = create_ce_flags(baselen + len, stage);
+       memcpy(ce->name, base, baselen);
+       memcpy(ce->name + baselen, pathname, len+1);
+       memcpy(ce->sha1, sha1, 20);
+       return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths)
+{
+       const char *match;
+       int pathlen;
+
+       if (!paths)
+               return 1;
+       pathlen = strlen(path);
+       while ((match = *paths++) != NULL) {
+               int matchlen = strlen(match);
+
+               if (baselen >= matchlen) {
+                       /* If it doesn't match, move along... */
+                       if (strncmp(base, match, matchlen))
+                               continue;
+                       /* The base is a subdirectory of a path which was specified. */
+                       return 1;
+               }
+
+               /* Does the base match? */
+               if (strncmp(base, match, baselen))
+                       continue;
+
+               match += baselen;
+               matchlen -= baselen;
+
+               if (pathlen > matchlen)
+                       continue;
+
+               if (matchlen > pathlen) {
+                       if (match[pathlen] != '/')
+                               continue;
+                       if (!S_ISDIR(mode))
+                               continue;
+               }
+
+               if (strncmp(path, match, pathlen))
+                       continue;
+
+               return 1;
+       }
+       return 0;
+}
+
+int read_tree_recursive(struct tree *tree,
+                       const char *base, int baselen,
+                       int stage, const char **match,
+                       read_tree_fn_t fn)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       if (parse_tree(tree))
+               return -1;
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+
+       while (tree_entry(&desc, &entry)) {
+               if (!match_tree_entry(base, baselen, entry.path, entry.mode, match))
+                       continue;
+
+               switch (fn(entry.sha1, base, baselen, entry.path, entry.mode, stage)) {
+               case 0:
+                       continue;
+               case READ_TREE_RECURSIVE:
+                       break;;
+               default:
+                       return -1;
+               }
+               if (S_ISDIR(entry.mode)) {
+                       int retval;
+                       char *newbase;
+
+                       newbase = xmalloc(baselen + 1 + entry.pathlen);
+                       memcpy(newbase, base, baselen);
+                       memcpy(newbase + baselen, entry.path, entry.pathlen);
+                       newbase[baselen + entry.pathlen] = '/';
+                       retval = read_tree_recursive(lookup_tree(entry.sha1),
+                                                    newbase,
+                                                    baselen + entry.pathlen + 1,
+                                                    stage, match, fn);
+                       free(newbase);
+                       if (retval)
+                               return -1;
+                       continue;
+               }
+       }
+       return 0;
+}
+
+int read_tree(struct tree *tree, int stage, const char **match)
+{
+       return read_tree_recursive(tree, "", 0, stage, match, read_one_entry);
+}
+
+struct tree *lookup_tree(const unsigned char *sha1)
+{
+       struct object *obj = lookup_object(sha1);
+       if (!obj) {
+               struct tree *ret = xcalloc(1, sizeof(struct tree));
+               created_object(sha1, &ret->object);
+               ret->object.type = tree_type;
+               return ret;
+       }
+       if (!obj->type)
+               obj->type = tree_type;
+       if (obj->type != tree_type) {
+               error("Object %s is a %s, not a tree", 
+                     sha1_to_hex(sha1), obj->type);
+               return NULL;
+       }
+       return (struct tree *) obj;
+}
+
+static int track_tree_refs(struct tree *item)
+{
+       int n_refs = 0, i;
+       struct object_refs *refs;
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       /* Count how many entries there are.. */
+       desc.buf = item->buffer;
+       desc.size = item->size;
+       while (desc.size) {
+               n_refs++;
+               update_tree_entry(&desc);
+       }
+
+       /* Allocate object refs and walk it again.. */
+       i = 0;
+       refs = alloc_object_refs(n_refs);
+       desc.buf = item->buffer;
+       desc.size = item->size;
+       while (tree_entry(&desc, &entry)) {
+               struct object *obj;
+
+               if (S_ISDIR(entry.mode))
+                       obj = &lookup_tree(entry.sha1)->object;
+               else
+                       obj = &lookup_blob(entry.sha1)->object;
+               refs->ref[i++] = obj;
+       }
+       set_object_refs(&item->object, refs);
+       return 0;
+}
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
+{
+       if (item->object.parsed)
+               return 0;
+       item->object.parsed = 1;
+       item->buffer = buffer;
+       item->size = size;
+
+       if (track_object_refs)
+               track_tree_refs(item);
+       return 0;
+}
+
+int parse_tree(struct tree *item)
+{
+        char type[20];
+        void *buffer;
+        unsigned long size;
+
+       if (item->object.parsed)
+               return 0;
+       buffer = read_sha1_file(item->object.sha1, type, &size);
+       if (!buffer)
+               return error("Could not read %s",
+                            sha1_to_hex(item->object.sha1));
+       if (strcmp(type, tree_type)) {
+               free(buffer);
+               return error("Object %s not a tree",
+                            sha1_to_hex(item->object.sha1));
+       }
+       return parse_tree_buffer(item, buffer, size);
+}
+
+struct tree *parse_tree_indirect(const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       do {
+               if (!obj)
+                       return NULL;
+               if (obj->type == tree_type)
+                       return (struct tree *) obj;
+               else if (obj->type == commit_type)
+                       obj = &(((struct commit *) obj)->tree->object);
+               else if (obj->type == tag_type)
+                       obj = ((struct tag *) obj)->tagged;
+               else
+                       return NULL;
+               if (!obj->parsed)
+                       parse_object(obj->sha1);
+       } while (1);
+}
diff --git a/tree.h b/tree.h
new file mode 100644 (file)
index 0000000..dd25c53
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,33 @@
+#ifndef TREE_H
+#define TREE_H
+
+#include "object.h"
+
+extern const char *tree_type;
+
+struct tree {
+       struct object object;
+       void *buffer;
+       unsigned long size;
+};
+
+struct tree *lookup_tree(const unsigned char *sha1);
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
+
+int parse_tree(struct tree *tree);
+
+/* Parses and returns the tree in the given ent, chasing tags and commits. */
+struct tree *parse_tree_indirect(const unsigned char *sha1);
+
+#define READ_TREE_RECURSIVE 1
+typedef int (*read_tree_fn_t)(const unsigned char *, const char *, int, const char *, unsigned int, int);
+
+extern int read_tree_recursive(struct tree *tree,
+                              const char *base, int baselen,
+                              int stage, const char **match,
+                              read_tree_fn_t fn);
+
+extern int read_tree(struct tree *tree, int stage, const char **paths);
+
+#endif /* TREE_H */
diff --git a/unpack-file.c b/unpack-file.c
new file mode 100644 (file)
index 0000000..ccddf1d
--- /dev/null
@@ -0,0 +1,40 @@
+#include "cache.h"
+#include "blob.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+       static char path[50];
+       void *buf;
+       char type[100];
+       unsigned long size;
+       int fd;
+
+       buf = read_sha1_file(sha1, type, &size);
+       if (!buf || strcmp(type, blob_type))
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+
+       strcpy(path, ".merge_file_XXXXXX");
+       fd = mkstemp(path);
+       if (fd < 0)
+               die("unable to create temp-file");
+       if (write(fd, buf, size) != size)
+               die("unable to write temp-file");
+       close(fd);
+       return path;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+
+       if (argc != 2)
+               usage("git-unpack-file <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       puts(create_temp_file(sha1));
+       return 0;
+}
diff --git a/unpack-objects.c b/unpack-objects.c
new file mode 100644 (file)
index 0000000..3b824b0
--- /dev/null
@@ -0,0 +1,316 @@
+#include "cache.h"
+#include "object.h"
+#include "delta.h"
+#include "pack.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
+
+#include <sys/time.h>
+
+static int dry_run, quiet;
+static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file";
+
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned long offset, len, eof;
+static SHA_CTX ctx;
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void * fill(int min)
+{
+       if (min <= len)
+               return buffer + offset;
+       if (eof)
+               die("unable to fill input");
+       if (min > sizeof(buffer))
+               die("cannot fill %d bytes", min);
+       if (offset) {
+               SHA1_Update(&ctx, buffer, offset);
+               memcpy(buffer, buffer + offset, len);
+               offset = 0;
+       }
+       do {
+               int ret = xread(0, buffer + len, sizeof(buffer) - len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       die("read error on input: %s", strerror(errno));
+               }
+               len += ret;
+       } while (len < min);
+       return buffer;
+}
+
+static void use(int bytes)
+{
+       if (bytes > len)
+               die("used more bytes than were available");
+       len -= bytes;
+       offset += bytes;
+}
+
+static void *get_data(unsigned long size)
+{
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = fill(1);
+       stream.avail_in = len;
+       inflateInit(&stream);
+
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               use(len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       die("inflate returned %d\n", ret);
+               stream.next_in = fill(1);
+               stream.avail_in = len;
+       }
+       inflateEnd(&stream);
+       return buf;
+}
+
+struct delta_info {
+       unsigned char base_sha1[20];
+       unsigned long size;
+       void *delta;
+       struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+{
+       struct delta_info *info = xmalloc(sizeof(*info));
+
+       memcpy(info->base_sha1, base_sha1, 20);
+       info->size = size;
+       info->delta = delta;
+       info->next = delta_list;
+       delta_list = info;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+
+static void write_object(void *buf, unsigned long size, const char *type)
+{
+       unsigned char sha1[20];
+       if (write_sha1_file(buf, size, type, sha1) < 0)
+               die("failed to write object");
+       added_object(sha1, type, buf, size);
+}
+
+static int resolve_delta(const char *type,
+       void *base, unsigned long base_size, 
+       void *delta, unsigned long delta_size)
+{
+       void *result;
+       unsigned long result_size;
+
+       result = patch_delta(base, base_size,
+                            delta, delta_size,
+                            &result_size);
+       if (!result)
+               die("failed to apply delta");
+       free(delta);
+       write_object(result, result_size, type);
+       free(result);
+       return 0;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+{
+       struct delta_info **p = &delta_list;
+       struct delta_info *info;
+
+       while ((info = *p) != NULL) {
+               if (!memcmp(info->base_sha1, sha1, 20)) {
+                       *p = info->next;
+                       p = &delta_list;
+                       resolve_delta(type, data, size, info->delta, info->size);
+                       free(info);
+                       continue;
+               }
+               p = &info->next;
+       }
+}
+
+static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+{
+       void *buf = get_data(size);
+       const char *type;
+
+       switch (kind) {
+       case OBJ_COMMIT: type = commit_type; break;
+       case OBJ_TREE:   type = tree_type; break;
+       case OBJ_BLOB:   type = blob_type; break;
+       case OBJ_TAG:    type = tag_type; break;
+       default: die("bad type %d", kind);
+       }
+       if (!dry_run)
+               write_object(buf, size, type);
+       free(buf);
+       return 0;
+}
+
+static int unpack_delta_entry(unsigned long delta_size)
+{
+       void *delta_data, *base;
+       unsigned long base_size;
+       char type[20];
+       unsigned char base_sha1[20];
+       int result;
+
+       memcpy(base_sha1, fill(20), 20);
+       use(20);
+
+       delta_data = get_data(delta_size);
+       if (dry_run) {
+               free(delta_data);
+               return 0;
+       }
+
+       if (!has_sha1_file(base_sha1)) {
+               add_delta_to_list(base_sha1, delta_data, delta_size);
+               return 0;
+       }
+       base = read_sha1_file(base_sha1, type, &base_size);
+       if (!base)
+               die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
+       result = resolve_delta(type, base, base_size, delta_data, delta_size);
+       free(base);
+       return result;
+}
+
+static void unpack_one(unsigned nr, unsigned total)
+{
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+       enum object_type type;
+
+       pack = fill(1);
+       c = *pack;
+       use(1);
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               pack = fill(1);
+               c = *pack++;
+               use(1);
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       if (!quiet) {
+               static unsigned long last_sec;
+               static unsigned last_percent;
+               struct timeval now;
+               unsigned percentage = (nr * 100) / total;
+
+               gettimeofday(&now, NULL);
+               if (percentage != last_percent || now.tv_sec != last_sec) {
+                       last_sec = now.tv_sec;
+                       last_percent = percentage;
+                       fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+               }
+       }
+       switch (type) {
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               unpack_non_delta_entry(type, size);
+               return;
+       case OBJ_DELTA:
+               unpack_delta_entry(size);
+               return;
+       default:
+               die("bad object type %d", type);
+       }
+}
+
+/*
+ * We unpack from the end, older files first. Now, usually
+ * there are deltas etc, so we'll not actually write the
+ * objects in that order, but we might as well try..
+ */
+static void unpack_all(void)
+{
+       int i;
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+       unsigned nr_objects = ntohl(hdr->hdr_entries);
+
+       if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
+               die("bad pack file");
+       if (!pack_version_ok(hdr->hdr_version))
+               die("unknown pack file version %d", ntohl(hdr->hdr_version));
+       fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+
+       use(sizeof(struct pack_header));
+       for (i = 0; i < nr_objects; i++)
+               unpack_one(i+1, nr_objects);
+       if (delta_list)
+               die("unresolved deltas left after unpacking");
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       unsigned char sha1[20];
+
+       setup_git_directory();
+
+       quiet = !isatty(2);
+
+       for (i = 1 ; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       if (!strcmp(arg, "-n")) {
+                               dry_run = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-q")) {
+                               quiet = 1;
+                               continue;
+                       }
+                       usage(unpack_usage);
+               }
+
+               /* We don't take any non-flag arguments now.. Maybe some day */
+               usage(unpack_usage);
+       }
+       SHA1_Init(&ctx);
+       unpack_all();
+       SHA1_Update(&ctx, buffer, offset);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(fill(20), sha1, 20))
+               die("final sha1 did not match");
+       use(20);
+
+       /* Write the last part of the buffer to stdout */
+       while (len) {
+               int ret = xwrite(1, buffer + offset, len);
+               if (ret <= 0)
+                       break;
+               len -= ret;
+               offset += ret;
+       }
+
+       /* All done */
+       if (!quiet)
+               fprintf(stderr, "\n");
+       return 0;
+}
diff --git a/update-index.c b/update-index.c
new file mode 100644 (file)
index 0000000..fbccc4a
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+#include "cache-tree.h"
+#include "tree-walk.h"
+
+/*
+ * Default to not allowing changes to the list of files. The
+ * tool doesn't actually care, but this makes it harder to add
+ * files to the revision control by mistake by doing something
+ * like "git-update-index *" and suddenly having all the object
+ * files be revision controlled.
+ */
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int info_only;
+static int force_remove;
+static int verbose;
+static int mark_valid_only = 0;
+#define MARK_VALID 1
+#define UNMARK_VALID 2
+
+static void report(const char *fmt, ...)
+{
+       va_list vp;
+
+       if (!verbose)
+               return;
+
+       va_start(vp, fmt);
+       vprintf(fmt, vp);
+       putchar('\n');
+       va_end(vp);
+}
+
+static int mark_valid(const char *path)
+{
+       int namelen = strlen(path);
+       int pos = cache_name_pos(path, namelen);
+       if (0 <= pos) {
+               switch (mark_valid_only) {
+               case MARK_VALID:
+                       active_cache[pos]->ce_flags |= htons(CE_VALID);
+                       break;
+               case UNMARK_VALID:
+                       active_cache[pos]->ce_flags &= ~htons(CE_VALID);
+                       break;
+               }
+               cache_tree_invalidate_path(active_cache_tree, path);
+               active_cache_changed = 1;
+               return 0;
+       }
+       return -1;
+}
+
+static int add_file_to_cache(const char *path)
+{
+       int size, namelen, option, status;
+       struct cache_entry *ce;
+       struct stat st;
+
+       status = lstat(path, &st);
+
+       /* We probably want to do this in remove_file_from_cache() and
+        * add_cache_entry() instead...
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
+       if (status < 0 || S_ISDIR(st.st_mode)) {
+               /* When we used to have "path" and now we want to add
+                * "path/file", we need a way to remove "path" before
+                * being able to add "path/file".  However,
+                * "git-update-index --remove path" would not work.
+                * --force-remove can be used but this is more user
+                * friendly, especially since we can do the opposite
+                * case just fine without --force-remove.
+                */
+               if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
+                       if (allow_remove) {
+                               if (remove_file_from_cache(path))
+                                       return error("%s: cannot remove from the index",
+                                                    path);
+                               else
+                                       return 0;
+                       } else if (status < 0) {
+                               return error("%s: does not exist and --remove not passed",
+                                            path);
+                       }
+               }
+               if (0 == status)
+                       return error("%s: is a directory - add files inside instead",
+                                    path);
+               else
+                       return error("lstat(\"%s\"): %s", path,
+                                    strerror(errno));
+       }
+
+       namelen = strlen(path);
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = htons(namelen);
+       fill_stat_cache_info(ce, &st);
+
+       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.
+                */
+               int pos = cache_name_pos(path, namelen);
+               if (0 <= pos)
+                       ce->ce_mode = active_cache[pos]->ce_mode;
+       }
+
+       if (index_path(ce->sha1, path, &st, !info_only))
+               return -1;
+       option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+       option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+       if (add_cache_entry(ce, option))
+               return error("%s: cannot add to the index - missing --add option?",
+                            path);
+       return 0;
+}
+
+static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
+                        const char *path, int stage)
+{
+       int size, len, option;
+       struct cache_entry *ce;
+
+       if (!verify_path(path))
+               return -1;
+
+       len = strlen(path);
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, path, len);
+       ce->ce_flags = create_ce_flags(len, stage);
+       ce->ce_mode = create_ce_mode(mode);
+       if (assume_unchanged)
+               ce->ce_flags |= htons(CE_VALID);
+       option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+       option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+       if (add_cache_entry(ce, option))
+               return error("%s: cannot add to the index - missing --add option?",
+                            path);
+       report("add '%s'", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
+       return 0;
+}
+
+static void chmod_path(int flip, const char *path)
+{
+       int pos;
+       struct cache_entry *ce;
+       unsigned int mode;
+
+       pos = cache_name_pos(path, strlen(path));
+       if (pos < 0)
+               goto fail;
+       ce = active_cache[pos];
+       mode = ntohl(ce->ce_mode);
+       if (!S_ISREG(mode))
+               goto fail;
+       switch (flip) {
+       case '+':
+               ce->ce_mode |= htonl(0111); break;
+       case '-':
+               ce->ce_mode &= htonl(~0111); break;
+       default:
+               goto fail;
+       }
+       cache_tree_invalidate_path(active_cache_tree, path);
+       active_cache_changed = 1;
+       report("chmod %cx '%s'", flip, path);
+       return;
+ fail:
+       die("git-update-index: cannot chmod %cx '%s'", flip, path);
+}
+
+static struct lock_file lock_file;
+
+static void update_one(const char *path, const char *prefix, int prefix_length)
+{
+       const char *p = prefix_path(prefix, prefix_length, path);
+       if (!verify_path(p)) {
+               fprintf(stderr, "Ignoring path %s\n", path);
+               goto free_return;
+       }
+       if (mark_valid_only) {
+               if (mark_valid(p))
+                       die("Unable to mark file %s", path);
+               goto free_return;
+       }
+       cache_tree_invalidate_path(active_cache_tree, path);
+
+       if (force_remove) {
+               if (remove_file_from_cache(p))
+                       die("git-update-index: unable to remove %s", path);
+               report("remove '%s'", path);
+               goto free_return;
+       }
+       if (add_file_to_cache(p))
+               die("Unable to process file %s", path);
+       report("add '%s'", path);
+ free_return:
+       if (p < path || p > path + strlen(path))
+               free((char*)p);
+}
+
+static void read_index_info(int line_termination)
+{
+       struct strbuf buf;
+       strbuf_init(&buf);
+       while (1) {
+               char *ptr, *tab;
+               char *path_name;
+               unsigned char sha1[20];
+               unsigned int mode;
+               int stage;
+
+               /* This reads lines formatted in one of three formats:
+                *
+                * (1) mode         SP sha1          TAB path
+                * The first format is what "git-apply --index-info"
+                * reports, and used to reconstruct a partial tree
+                * that is used for phony merge base tree when falling
+                * back on 3-way merge.
+                *
+                * (2) mode SP type SP sha1          TAB path
+                * The second format is to stuff git-ls-tree output
+                * into the index file.
+                * 
+                * (3) mode         SP sha1 SP stage TAB path
+                * This format is to put higher order stages into the
+                * index file and matches git-ls-files --stage output.
+                */
+               read_line(&buf, stdin, line_termination);
+               if (buf.eof)
+                       break;
+
+               mode = strtoul(buf.buf, &ptr, 8);
+               if (ptr == buf.buf || *ptr != ' ')
+                       goto bad_line;
+
+               tab = strchr(ptr, '\t');
+               if (!tab || tab - ptr < 41)
+                       goto bad_line;
+
+               if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') {
+                       stage = tab[-1] - '0';
+                       ptr = tab + 1; /* point at the head of path */
+                       tab = tab - 2; /* point at tail of sha1 */
+               }
+               else {
+                       stage = 0;
+                       ptr = tab + 1; /* point at the head of path */
+               }
+
+               if (get_sha1_hex(tab - 40, sha1) || tab[-41] != ' ')
+                       goto bad_line;
+
+               if (line_termination && ptr[0] == '"')
+                       path_name = unquote_c_style(ptr, NULL);
+               else
+                       path_name = ptr;
+
+               if (!verify_path(path_name)) {
+                       fprintf(stderr, "Ignoring path %s\n", path_name);
+                       if (path_name != ptr)
+                               free(path_name);
+                       continue;
+               }
+               cache_tree_invalidate_path(active_cache_tree, path_name);
+
+               if (!mode) {
+                       /* mode == 0 means there is no such path -- remove */
+                       if (remove_file_from_cache(path_name))
+                               die("git-update-index: unable to remove %s",
+                                   ptr);
+               }
+               else {
+                       /* mode ' ' sha1 '\t' name
+                        * ptr[-1] points at tab,
+                        * ptr[-41] is at the beginning of sha1
+                        */
+                       ptr[-42] = ptr[-1] = 0;
+                       if (add_cacheinfo(mode, sha1, path_name, stage))
+                               die("git-update-index: unable to update %s",
+                                   path_name);
+               }
+               if (path_name != ptr)
+                       free(path_name);
+               continue;
+
+       bad_line:
+               die("malformed index info %s", buf.buf);
+       }
+}
+
+static const char update_index_usage[] =
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+                                       unsigned char *ent, const char *path,
+                                       int namelen, int stage)
+{
+       unsigned mode;
+       unsigned char sha1[20];
+       int size;
+       struct cache_entry *ce;
+
+       if (get_tree_entry(ent, path, sha1, &mode)) {
+               if (which)
+                       error("%s: not in %s branch.", path, which);
+               return NULL;
+       }
+       if (mode == S_IFDIR) {
+               if (which)
+                       error("%s: not a blob in %s branch.", path, which);
+               return NULL;
+       }
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = create_ce_flags(namelen, stage);
+       ce->ce_mode = create_ce_mode(mode);
+       return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+       int namelen = strlen(path);
+       int pos;
+       int ret = 0;
+       struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+       /* See if there is such entry in the index. */
+       pos = cache_name_pos(path, namelen);
+       if (pos < 0) {
+               /* If there isn't, either it is unmerged, or
+                * resolved as "removed" by mistake.  We do not
+                * want to do anything in the former case.
+                */
+               pos = -pos-1;
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen)) {
+                               fprintf(stderr,
+                                       "%s: skipping still unmerged path.\n",
+                                       path);
+                               goto free_return;
+                       }
+               }
+       }
+
+       /* Grab blobs from given path from HEAD and MERGE_HEAD,
+        * stuff HEAD version in stage #2,
+        * stuff MERGE_HEAD version in stage #3.
+        */
+       ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+       ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+       if (!ce_2 || !ce_3) {
+               ret = -1;
+               goto free_return;
+       }
+       if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+           ce_2->ce_mode == ce_3->ce_mode) {
+               fprintf(stderr, "%s: identical in both, skipping.\n",
+                       path);
+               goto free_return;
+       }
+
+       cache_tree_invalidate_path(active_cache_tree, path);
+       remove_file_from_cache(path);
+       if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
+               error("%s: cannot add our version to the index.", path);
+               ret = -1;
+               goto free_return;
+       }
+       if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+               return 0;
+       error("%s: cannot add their version to the index.", path);
+       ret = -1;
+ free_return:
+       free(ce_2);
+       free(ce_3);
+       return ret;
+}
+
+static void read_head_pointers(void)
+{
+       if (read_ref(git_path("HEAD"), head_sha1))
+               die("No HEAD -- no initial commit yet?\n");
+       if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+               fprintf(stderr, "Not in the middle of a merge.\n");
+               exit(0);
+       }
+}
+
+static int do_unresolve(int ac, const char **av,
+                       const char *prefix, int prefix_length)
+{
+       int i;
+       int err = 0;
+
+       /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+        * are not doing a merge, so exit with success status.
+        */
+       read_head_pointers();
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               const char *p = prefix_path(prefix, prefix_length, arg);
+               err |= unresolve_one(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
+       }
+       return err;
+}
+
+static int do_reupdate(int ac, const char **av,
+                      const char *prefix, int prefix_length)
+{
+       /* Read HEAD and run update-index on paths that are
+        * merged and already different between index and HEAD.
+        */
+       int pos;
+       int has_head = 1;
+       const char **pathspec = get_pathspec(prefix, av + 1);
+
+       if (read_ref(git_path("HEAD"), head_sha1))
+               /* If there is no HEAD, that means it is an initial
+                * commit.  Update everything in the index.
+                */
+               has_head = 0;
+ redo:
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               struct cache_entry *old = NULL;
+               int save_nr;
+
+               if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+                       continue;
+               if (has_head)
+                       old = read_one_ent(NULL, head_sha1,
+                                          ce->name, ce_namelen(ce), 0);
+               if (old && ce->ce_mode == old->ce_mode &&
+                   !memcmp(ce->sha1, old->sha1, 20)) {
+                       free(old);
+                       continue; /* unchanged */
+               }
+               /* Be careful.  The working tree may not have the
+                * path anymore, in which case, under 'allow_remove',
+                * or worse yet 'allow_replace', active_nr may decrease.
+                */
+               save_nr = active_nr;
+               update_one(ce->name + prefix_length, prefix, prefix_length);
+               if (save_nr != active_nr)
+                       goto redo;
+       }
+       return 0;
+}
+
+int main(int argc, const char **argv)
+{
+       int i, newfd, entries, has_errors = 0, line_termination = '\n';
+       int allow_options = 1;
+       int read_from_stdin = 0;
+       const char *prefix = setup_git_directory();
+       int prefix_length = prefix ? strlen(prefix) : 0;
+       char set_executable_bit = 0;
+       unsigned int refresh_flags = 0;
+
+       git_config(git_default_config);
+
+       newfd = hold_lock_file_for_update(&lock_file, get_index_file());
+       if (newfd < 0)
+               die("unable to create new index file");
+
+       entries = read_cache();
+       if (entries < 0)
+               die("cache corrupted");
+
+       for (i = 1 ; i < argc; i++) {
+               const char *path = argv[i];
+
+               if (allow_options && *path == '-') {
+                       if (!strcmp(path, "--")) {
+                               allow_options = 0;
+                               continue;
+                       }
+                       if (!strcmp(path, "-q")) {
+                               refresh_flags |= REFRESH_QUIET;
+                               continue;
+                       }
+                       if (!strcmp(path, "--add")) {
+                               allow_add = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--replace")) {
+                               allow_replace = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--remove")) {
+                               allow_remove = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--unmerged")) {
+                               refresh_flags |= REFRESH_UNMERGED;
+                               continue;
+                       }
+                       if (!strcmp(path, "--refresh")) {
+                               has_errors |= refresh_cache(refresh_flags);
+                               continue;
+                       }
+                       if (!strcmp(path, "--really-refresh")) {
+                               has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags);
+                               continue;
+                       }
+                       if (!strcmp(path, "--cacheinfo")) {
+                               unsigned char sha1[20];
+                               unsigned int mode;
+
+                               if (i+3 >= argc)
+                                       die("git-update-index: --cacheinfo <mode> <sha1> <path>");
+
+                               if ((sscanf(argv[i+1], "%o", &mode) != 1) ||
+                                   get_sha1_hex(argv[i+2], sha1) ||
+                                   add_cacheinfo(mode, sha1, argv[i+3], 0))
+                                       die("git-update-index: --cacheinfo"
+                                           " cannot add %s", argv[i+3]);
+                               i += 3;
+                               continue;
+                       }
+                       if (!strcmp(path, "--chmod=-x") ||
+                           !strcmp(path, "--chmod=+x")) {
+                               if (argc <= i+1)
+                                       die("git-update-index: %s <path>", path);
+                               set_executable_bit = path[8];
+                               continue;
+                       }
+                       if (!strcmp(path, "--assume-unchanged")) {
+                               mark_valid_only = MARK_VALID;
+                               continue;
+                       }
+                       if (!strcmp(path, "--no-assume-unchanged")) {
+                               mark_valid_only = UNMARK_VALID;
+                               continue;
+                       }
+                       if (!strcmp(path, "--info-only")) {
+                               info_only = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "--force-remove")) {
+                               force_remove = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "-z")) {
+                               line_termination = 0;
+                               continue;
+                       }
+                       if (!strcmp(path, "--stdin")) {
+                               if (i != argc - 1)
+                                       die("--stdin must be at the end");
+                               read_from_stdin = 1;
+                               break;
+                       }
+                       if (!strcmp(path, "--index-info")) {
+                               if (i != argc - 1)
+                                       die("--index-info must be at the end");
+                               allow_add = allow_replace = allow_remove = 1;
+                               read_index_info(line_termination);
+                               break;
+                       }
+                       if (!strcmp(path, "--unresolve")) {
+                               has_errors = do_unresolve(argc - i, argv + i,
+                                                         prefix, prefix_length);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
+                       if (!strcmp(path, "--again")) {
+                               has_errors = do_reupdate(argc - i, argv + i,
+                                                        prefix, prefix_length);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
+                       if (!strcmp(path, "--ignore-missing")) {
+                               refresh_flags |= REFRESH_IGNORE_MISSING;
+                               continue;
+                       }
+                       if (!strcmp(path, "--verbose")) {
+                               verbose = 1;
+                               continue;
+                       }
+                       if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+                               usage(update_index_usage);
+                       die("unknown option %s", path);
+               }
+               update_one(path, prefix, prefix_length);
+               if (set_executable_bit)
+                       chmod_path(set_executable_bit, path);
+       }
+       if (read_from_stdin) {
+               struct strbuf buf;
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       const char *p;
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       update_one(p, NULL, 0);
+                       if (set_executable_bit)
+                               chmod_path(set_executable_bit, p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char*) p);
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
+ finish:
+       if (active_cache_changed) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_lock_file(&lock_file))
+                       die("Unable to write new index file");
+       }
+
+       return has_errors ? 1 : 0;
+}
diff --git a/update-ref.c b/update-ref.c
new file mode 100644 (file)
index 0000000..a1e6bb9
--- /dev/null
@@ -0,0 +1,56 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
+
+int main(int argc, char **argv)
+{
+       const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+       struct ref_lock *lock;
+       unsigned char sha1[20], oldsha1[20];
+       int i;
+
+       setup_git_directory();
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("-m", argv[i])) {
+                       if (i+1 >= argc)
+                               usage(git_update_ref_usage);
+                       msg = argv[++i];
+                       if (!*msg)
+                               die("Refusing to perform update with empty message.");
+                       if (strchr(msg, '\n'))
+                               die("Refusing to perform update with \\n in message.");
+                       continue;
+               }
+               if (!refname) {
+                       refname = argv[i];
+                       continue;
+               }
+               if (!value) {
+                       value = argv[i];
+                       continue;
+               }
+               if (!oldval) {
+                       oldval = argv[i];
+                       continue;
+               }
+       }
+       if (!refname || !value)
+               usage(git_update_ref_usage);
+
+       if (get_sha1(value, sha1))
+               die("%s: not a valid SHA1", value);
+       memset(oldsha1, 0, 20);
+       if (oldval && get_sha1(oldval, oldsha1))
+               die("%s: not a valid old SHA1", oldval);
+
+       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+       if (!lock)
+               return 1;
+       if (write_ref_sha1(lock, sha1, msg) < 0)
+               return 1;
+       return 0;
+}
diff --git a/update-server-info.c b/update-server-info.c
new file mode 100644 (file)
index 0000000..0b6c383
--- /dev/null
@@ -0,0 +1,25 @@
+#include "cache.h"
+
+static const char update_server_info_usage[] =
+"git-update-server-info [--force]";
+
+int main(int ac, char **av)
+{
+       int i;
+       int force = 0;
+       for (i = 1; i < ac; i++) {
+               if (av[i][0] == '-') {
+                       if (!strcmp("--force", av[i]) ||
+                           !strcmp("-f", av[i]))
+                               force = 1;
+                       else
+                               usage(update_server_info_usage);
+               }
+       }
+       if (i != ac)
+               usage(update_server_info_usage);
+
+       setup_git_directory();
+
+       return !!update_server_info(force);
+}
diff --git a/upload-pack.c b/upload-pack.c
new file mode 100644 (file)
index 0000000..47560c9
--- /dev/null
@@ -0,0 +1,292 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
+#include "exec_cmd.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)
+#define MAX_HAS 256
+#define MAX_NEEDS 256
+static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
+static int use_thin_pack = 0;
+static unsigned char has_sha1[MAX_HAS][20];
+static unsigned char needs_sha1[MAX_NEEDS][20];
+static unsigned int timeout = 0;
+
+static void reset_timeout(void)
+{
+       alarm(timeout);
+}
+
+static int strip(char *line, int len)
+{
+       if (len && line[len-1] == '\n')
+               line[--len] = 0;
+       return len;
+}
+
+static void create_pack_file(void)
+{
+       int fd[2];
+       pid_t pid;
+       int create_full_pack = (nr_our_refs == nr_needs && !nr_has);
+
+       if (pipe(fd) < 0)
+               die("git-upload-pack: unable to create pipe");
+       pid = fork();
+       if (pid < 0)
+               die("git-upload-pack: unable to fork git-rev-list");
+
+       if (!pid) {
+               int i;
+               int args;
+               const char **argv;
+               char *buf;
+               char **p;
+
+               if (create_full_pack) {
+                       args = 10;
+                       use_thin_pack = 0; /* no point doing it */
+               }
+               else
+                       args = nr_has + nr_needs + 5;
+               p = xmalloc(args * sizeof(char *));
+               argv = (const char **) p;
+               buf = xmalloc(args * 45);
+
+               dup2(fd[1], 1);
+               close(0);
+               close(fd[0]);
+               close(fd[1]);
+               *p++ = "rev-list";
+               *p++ = use_thin_pack ? "--objects-edge" : "--objects";
+               if (create_full_pack || MAX_NEEDS <= nr_needs)
+                       *p++ = "--all";
+               else {
+                       for (i = 0; i < nr_needs; i++) {
+                               *p++ = buf;
+                               memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
+                               buf += 41;
+                       }
+               }
+               if (!create_full_pack)
+                       for (i = 0; i < nr_has; i++) {
+                               *p++ = buf;
+                               *buf++ = '^';
+                               memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
+                               buf += 41;
+                       }
+               *p++ = NULL;
+               execv_git_cmd(argv);
+               die("git-upload-pack: unable to exec git-rev-list");
+       }
+       dup2(fd[0], 0);
+       close(fd[0]);
+       close(fd[1]);
+       execl_git_cmd("pack-objects", "--stdout", NULL);
+       die("git-upload-pack: unable to exec git-pack-objects");
+}
+
+static int got_sha1(char *hex, unsigned char *sha1)
+{
+       if (get_sha1_hex(hex, sha1))
+               die("git-upload-pack: expected SHA1 object, got '%s'", hex);
+       if (!has_sha1_file(sha1))
+               return 0;
+       if (nr_has < MAX_HAS) {
+               struct object *o = lookup_object(sha1);
+               if (!(o && o->parsed))
+                       o = parse_object(sha1);
+               if (!o)
+                       die("oops (%s)", sha1_to_hex(sha1));
+               if (o->type == commit_type) {
+                       struct commit_list *parents;
+                       if (o->flags & THEY_HAVE)
+                               return 0;
+                       o->flags |= THEY_HAVE;
+                       for (parents = ((struct commit*)o)->parents;
+                            parents;
+                            parents = parents->next)
+                               parents->item->object.flags |= THEY_HAVE;
+               }
+               memcpy(has_sha1[nr_has++], sha1, 20);
+       }
+       return 1;
+}
+
+static int get_common_commits(void)
+{
+       static char line[1000];
+       unsigned char sha1[20], last_sha1[20];
+       int len;
+
+       track_object_refs = 0;
+       save_commit_buffer = 0;
+
+       for(;;) {
+               len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
+
+               if (!len) {
+                       if (nr_has == 0 || multi_ack)
+                               packet_write(1, "NAK\n");
+                       continue;
+               }
+               len = strip(line, len);
+               if (!strncmp(line, "have ", 5)) {
+                       if (got_sha1(line+5, sha1) &&
+                                       (multi_ack || nr_has == 1)) {
+                               if (nr_has >= MAX_HAS)
+                                       multi_ack = 0;
+                               packet_write(1, "ACK %s%s\n",
+                                       sha1_to_hex(sha1),
+                                       multi_ack ?  " continue" : "");
+                               if (multi_ack)
+                                       memcpy(last_sha1, sha1, 20);
+                       }
+                       continue;
+               }
+               if (!strcmp(line, "done")) {
+                       if (nr_has > 0) {
+                               if (multi_ack)
+                                       packet_write(1, "ACK %s\n",
+                                                       sha1_to_hex(last_sha1));
+                               return 0;
+                       }
+                       packet_write(1, "NAK\n");
+                       return -1;
+               }
+               die("git-upload-pack: expected SHA1 list, got '%s'", line);
+       }
+}
+
+static int receive_needs(void)
+{
+       static char line[1000];
+       int len, needs;
+
+       needs = 0;
+       for (;;) {
+               struct object *o;
+               unsigned char dummy[20], *sha1_buf;
+               len = packet_read_line(0, line, sizeof(line));
+               reset_timeout();
+               if (!len)
+                       return needs;
+
+               sha1_buf = dummy;
+               if (needs == MAX_NEEDS) {
+                       fprintf(stderr,
+                               "warning: supporting only a max of %d requests. "
+                               "sending everything instead.\n",
+                               MAX_NEEDS);
+               }
+               else if (needs < MAX_NEEDS)
+                       sha1_buf = needs_sha1[needs];
+
+               if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
+                       die("git-upload-pack: protocol error, "
+                           "expected to get sha, not '%s'", line);
+               if (strstr(line+45, "multi_ack"))
+                       multi_ack = 1;
+               if (strstr(line+45, "thin-pack"))
+                       use_thin_pack = 1;
+
+               /* We have sent all our refs already, and the other end
+                * should have chosen out of them; otherwise they are
+                * asking for nonsense.
+                *
+                * Hmph.  We may later want to allow "want" line that
+                * asks for something like "master~10" (symbolic)...
+                * would it make sense?  I don't know.
+                */
+               o = lookup_object(sha1_buf);
+               if (!o || !(o->flags & OUR_REF))
+                       die("git-upload-pack: not our ref %s", line+5);
+               if (!(o->flags & WANTED)) {
+                       o->flags |= WANTED;
+                       needs++;
+               }
+       }
+}
+
+static int send_ref(const char *refname, const unsigned char *sha1)
+{
+       static char *capabilities = "multi_ack thin-pack";
+       struct object *o = parse_object(sha1);
+
+       if (!o)
+               die("git-upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+
+       if (capabilities)
+               packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
+                       0, capabilities);
+       else
+               packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       capabilities = NULL;
+       if (!(o->flags & OUR_REF)) {
+               o->flags |= OUR_REF;
+               nr_our_refs++;
+       }
+       if (o->type == tag_type) {
+               o = deref_tag(o, refname, 0);
+               packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+       }
+       return 0;
+}
+
+static int upload_pack(void)
+{
+       reset_timeout();
+       head_ref(send_ref);
+       for_each_ref(send_ref);
+       packet_flush(1);
+       nr_needs = receive_needs();
+       if (!nr_needs)
+               return 0;
+       get_common_commits();
+       create_pack_file();
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       char *dir;
+       int i;
+       int strict = 0;
+
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "--strict")) {
+                       strict = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--timeout=", 10)) {
+                       timeout = atoi(arg+10);
+                       continue;
+               }
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+       }
+       
+       if (i != argc-1)
+               usage(upload_pack_usage);
+       dir = argv[i];
+
+       if (!enter_repo(dir, strict))
+               die("'%s': unable to chdir or not a git archive", dir);
+
+       upload_pack();
+       return 0;
+}
diff --git a/usage.c b/usage.c
new file mode 100644 (file)
index 0000000..1fa924c
--- /dev/null
+++ b/usage.c
@@ -0,0 +1,39 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "git-compat-util.h"
+
+static void report(const char *prefix, const char *err, va_list params)
+{
+       fputs(prefix, stderr);
+       vfprintf(stderr, err, params);
+       fputs("\n", stderr);
+}
+
+void usage(const char *err)
+{
+       fprintf(stderr, "usage: %s\n", err);
+       exit(129);
+}
+
+void die(const char *err, ...)
+{
+       va_list params;
+
+       va_start(params, err);
+       report("fatal: ", err, params);
+       va_end(params);
+       exit(128);
+}
+
+int error(const char *err, ...)
+{
+       va_list params;
+
+       va_start(params, err);
+       report("error: ", err, params);
+       va_end(params);
+       return -1;
+}
diff --git a/var.c b/var.c
new file mode 100644 (file)
index 0000000..a57a33b
--- /dev/null
+++ b/var.c
@@ -0,0 +1,78 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * 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>]";
+
+struct git_var {
+       const char *name;
+       const char *(*read)(int);
+};
+static struct git_var git_vars[] = {
+       { "GIT_COMMITTER_IDENT", git_committer_info },
+       { "GIT_AUTHOR_IDENT",   git_author_info },
+       { "", NULL },
+};
+
+static void list_vars(void)
+{
+       struct git_var *ptr;
+       for(ptr = git_vars; ptr->read; ptr++) {
+               printf("%s=%s\n", ptr->name, ptr->read(0));
+       }
+}
+
+static const char *read_var(const char *var)
+{
+       struct git_var *ptr;
+       const char *val;
+       val = NULL;
+       for(ptr = git_vars; ptr->read; ptr++) {
+               if (strcmp(var, ptr->name) == 0) {
+                       val = ptr->read(1);
+                       break;
+               }
+       }
+       return val;
+}
+
+static int show_config(const char *var, const char *value)
+{
+       if (value)
+               printf("%s=%s\n", var, value);
+       else
+               printf("%s\n", var);
+       return git_default_config(var, value);
+}
+
+int main(int argc, char **argv)
+{
+       const char *val;
+       if (argc != 2) {
+               usage(var_usage);
+       }
+
+       setup_git_directory();
+       setup_ident();
+       val = NULL;
+
+       if (strcmp(argv[1], "-l") == 0) {
+               git_config(show_config);
+               list_vars();
+               return 0;
+       }
+       git_config(git_default_config);
+       val = read_var(argv[1]);
+       if (!val)
+               usage(var_usage);
+       
+       printf("%s\n", val);
+       
+       return 0;
+}
diff --git a/verify-pack.c b/verify-pack.c
new file mode 100644 (file)
index 0000000..c99db9d
--- /dev/null
@@ -0,0 +1,57 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_one_pack(char *arg, int verbose)
+{
+       int len = strlen(arg);
+       struct packed_git *g;
+       
+       while (1) {
+               /* Should name foo.idx, but foo.pack may be named;
+                * convert it to foo.idx
+                */
+               if (!strcmp(arg + len - 5, ".pack")) {
+                       strcpy(arg + len - 5, ".idx");
+                       len--;
+               }
+               /* Should name foo.idx now */
+               if ((g = add_packed_git(arg, len, 1)))
+                       break;
+               /* No?  did you name just foo? */
+               strcpy(arg + len, ".idx");
+               len += 4;
+               if ((g = add_packed_git(arg, len, 1)))
+                       break;
+               return error("packfile %s not found.", arg);
+       }
+       return verify_pack(g, verbose);
+}
+
+static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+
+int main(int ac, char **av)
+{
+       int errs = 0;
+       int verbose = 0;
+       int no_more_options = 0;
+
+       while (1 < ac) {
+               char path[PATH_MAX];
+
+               if (!no_more_options && av[1][0] == '-') {
+                       if (!strcmp("-v", av[1]))
+                               verbose = 1;
+                       else if (!strcmp("--", av[1]))
+                               no_more_options = 1;
+                       else
+                               usage(verify_pack_usage);
+               }
+               else {
+                       strcpy(path, av[1]);
+                       if (verify_one_pack(path, verbose))
+                               errs++;
+               }
+               ac--; av++;
+       }
+       return !!errs;
+}
diff --git a/write-tree.c b/write-tree.c
new file mode 100644 (file)
index 0000000..d6a6058
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+static int missing_ok = 0;
+
+static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
+
+static struct lock_file lock_file;
+
+int main(int argc, char **argv)
+{
+       int entries, was_valid, newfd;
+
+       setup_git_directory();
+
+       newfd = hold_lock_file_for_update(&lock_file, get_index_file());
+       entries = read_cache();
+       if (argc == 2) {
+               if (!strcmp(argv[1], "--missing-ok"))
+                       missing_ok = 1;
+               else
+                       die(write_tree_usage);
+       }
+       
+       if (argc > 2)
+               die("too many options");
+
+       if (entries < 0)
+               die("git-write-tree: error reading cache");
+
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       was_valid = cache_tree_fully_valid(active_cache_tree);
+       if (!was_valid) {
+               if (cache_tree_update(active_cache_tree,
+                                     active_cache, active_nr,
+                                     missing_ok, 0) < 0)
+                       die("git-write-tree: error building trees");
+               if (0 <= newfd) {
+                       if (!write_cache(newfd, active_cache, active_nr))
+                               commit_lock_file(&lock_file);
+               }
+               /* Not being able to write is fine -- we are only interested
+                * in updating the cache-tree part, and if the next caller
+                * ends up using the old index with unupdated cache-tree part
+                * it misses the work we did here, but that is just a
+                * performance penalty and not a big deal.
+                */
+       }
+       printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
+       return 0;
+}
diff --git a/xdiff-interface.c b/xdiff-interface.c
new file mode 100644 (file)
index 0000000..6a82da7
--- /dev/null
@@ -0,0 +1,104 @@
+#include "cache.h"
+#include "xdiff-interface.h"
+
+static int parse_num(char **cp_p, int *num_p)
+{
+       char *cp = *cp_p;
+       int num = 0;
+       int read_some;
+
+       while ('0' <= *cp && *cp <= '9')
+               num = num * 10 + *cp++ - '0';
+       if (!(read_some = cp - *cp_p))
+               return -1;
+       *cp_p = cp;
+       *num_p = num;
+       return 0;
+}
+
+int parse_hunk_header(char *line, int len,
+                     int *ob, int *on,
+                     int *nb, int *nn)
+{
+       char *cp;
+       cp = line + 4;
+       if (parse_num(&cp, ob)) {
+       bad_line:
+               return error("malformed diff output: %s", line);
+       }
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, on))
+                       goto bad_line;
+       }
+       else
+               *on = 1;
+       if (*cp++ != ' ' || *cp++ != '+')
+               goto bad_line;
+       if (parse_num(&cp, nb))
+               goto bad_line;
+       if (*cp == ',') {
+               cp++;
+               if (parse_num(&cp, nn))
+                       goto bad_line;
+       }
+       else
+               *nn = 1;
+       return -!!memcmp(cp, " @@", 3);
+}
+
+static void consume_one(void *priv_, char *s, unsigned long size)
+{
+       struct xdiff_emit_state *priv = priv_;
+       char *ep;
+       while (size) {
+               unsigned long this_size;
+               ep = memchr(s, '\n', size);
+               this_size = (ep == NULL) ? size : (ep - s + 1);
+               priv->consume(priv, s, this_size);
+               size -= this_size;
+               s += this_size;
+       }
+}
+
+int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
+{
+       struct xdiff_emit_state *priv = priv_;
+       int i;
+
+       for (i = 0; i < nbuf; i++) {
+               if (mb[i].ptr[mb[i].size-1] != '\n') {
+                       /* Incomplete line */
+                       priv->remainder = realloc(priv->remainder,
+                                                 priv->remainder_size +
+                                                 mb[i].size);
+                       memcpy(priv->remainder + priv->remainder_size,
+                              mb[i].ptr, mb[i].size);
+                       priv->remainder_size += mb[i].size;
+                       continue;
+               }
+
+               /* we have a complete line */
+               if (!priv->remainder) {
+                       consume_one(priv, mb[i].ptr, mb[i].size);
+                       continue;
+               }
+               priv->remainder = realloc(priv->remainder,
+                                         priv->remainder_size +
+                                         mb[i].size);
+               memcpy(priv->remainder + priv->remainder_size,
+                      mb[i].ptr, mb[i].size);
+               consume_one(priv, priv->remainder,
+                           priv->remainder_size + mb[i].size);
+               free(priv->remainder);
+               priv->remainder = NULL;
+               priv->remainder_size = 0;
+       }
+       if (priv->remainder) {
+               consume_one(priv, priv->remainder, priv->remainder_size);
+               free(priv->remainder);
+               priv->remainder = NULL;
+               priv->remainder_size = 0;
+       }
+       return 0;
+}
diff --git a/xdiff-interface.h b/xdiff-interface.h
new file mode 100644 (file)
index 0000000..1346908
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef XDIFF_INTERFACE_H
+#define XDIFF_INTERFACE_H
+
+#include "xdiff/xdiff.h"
+
+struct xdiff_emit_state;
+
+typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
+
+struct xdiff_emit_state {
+       xdiff_emit_consume_fn consume;
+       char *remainder;
+       unsigned long remainder_size;
+};
+
+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);
+
+#endif
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
new file mode 100644 (file)
index 0000000..2540e8a
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XDIFF_H)
+#define XDIFF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* #ifdef __cplusplus */
+
+
+#define XDF_NEED_MINIMAL (1 << 1)
+
+#define XDL_PATCH_NORMAL '-'
+#define XDL_PATCH_REVERSE '+'
+#define XDL_PATCH_MODEMASK ((1 << 8) - 1)
+#define XDL_PATCH_IGNOREBSPACE (1 << 8)
+
+#define XDL_EMIT_FUNCNAMES (1 << 0)
+
+#define XDL_MMB_READONLY (1 << 0)
+
+#define XDL_MMF_ATOMIC (1 << 0)
+
+#define XDL_BDOP_INS 1
+#define XDL_BDOP_CPY 2
+#define XDL_BDOP_INSB 3
+
+
+typedef struct s_mmfile {
+       char *ptr;
+       long size;
+} mmfile_t;
+
+typedef struct s_mmbuffer {
+       char *ptr;
+       long size;
+} mmbuffer_t;
+
+typedef struct s_xpparam {
+       unsigned long flags;
+} xpparam_t;
+
+typedef struct s_xdemitcb {
+       void *priv;
+       int (*outf)(void *, mmbuffer_t *, int);
+} xdemitcb_t;
+
+typedef struct s_xdemitconf {
+       long ctxlen;
+       unsigned long flags;
+} xdemitconf_t;
+
+typedef struct s_bdiffparam {
+       long bsize;
+} bdiffparam_t;
+
+
+#define xdl_malloc(x) malloc(x)
+#define xdl_free(ptr) free(ptr)
+#define xdl_realloc(ptr,x) realloc(ptr,x)
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size);
+void *xdl_mmfile_next(mmfile_t *mmf, long *size);
+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);
+
+#ifdef __cplusplus
+}
+#endif /* #ifdef __cplusplus */
+
+#endif /* #if !defined(XDIFF_H) */
+
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c
new file mode 100644 (file)
index 0000000..b95ade2
--- /dev/null
@@ -0,0 +1,569 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  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"
+
+
+
+#define XDL_MAX_COST_MIN 256
+#define XDL_HEUR_MIN_COST 256
+#define XDL_LINE_MAX (long)((1UL << (8 * sizeof(long) - 1)) - 1)
+#define XDL_SNAKE_CNT 20
+#define XDL_K_HEUR 4
+
+
+
+typedef struct s_xdpsplit {
+       long i1, i2;
+       int min_lo, min_hi;
+} xdpsplit_t;
+
+
+
+
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+                     unsigned long const *ha2, long off2, long lim2,
+                     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);
+
+
+
+
+
+/*
+ * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers.
+ * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both
+ * the forward diagonal starting from (off1, off2) and the backward diagonal
+ * starting from (lim1, lim2). If the K values on the same diagonal crosses
+ * returns the furthest point of reach. We might end up having to expensive
+ * cases using this algorithm is full, so a little bit of heuristic is needed
+ * to cut the search and to return a suboptimal point.
+ */
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+                     unsigned long const *ha2, long off2, long lim2,
+                     long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+                     xdalgoenv_t *xenv) {
+       long dmin = off1 - lim2, dmax = lim1 - off2;
+       long fmid = off1 - off2, bmid = lim1 - lim2;
+       long odd = (fmid - bmid) & 1;
+       long fmin = fmid, fmax = fmid;
+       long bmin = bmid, bmax = bmid;
+       long ec, d, i1, i2, prev1, best, dd, v, k;
+
+       /*
+        * Set initial diagonal values for both forward and backward path.
+        */
+       kvdf[fmid] = off1;
+       kvdb[bmid] = lim1;
+
+       for (ec = 1;; ec++) {
+               int got_snake = 0;
+
+               /*
+                * We need to extent the diagonal "domain" by one. If the next
+                * values exits the box boundaries we need to change it in the
+                * opposite direction because (max - min) must be a power of two.
+                * Also we initialize the extenal K value to -1 so that we can
+                * avoid extra conditions check inside the core loop.
+                */
+               if (fmin > dmin)
+                       kvdf[--fmin - 1] = -1;
+               else
+                       ++fmin;
+               if (fmax < dmax)
+                       kvdf[++fmax + 1] = -1;
+               else
+                       --fmax;
+
+               for (d = fmax; d >= fmin; d -= 2) {
+                       if (kvdf[d - 1] >= kvdf[d + 1])
+                               i1 = kvdf[d - 1] + 1;
+                       else
+                               i1 = kvdf[d + 1];
+                       prev1 = i1;
+                       i2 = i1 - d;
+                       for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++);
+                       if (i1 - prev1 > xenv->snake_cnt)
+                               got_snake = 1;
+                       kvdf[d] = i1;
+                       if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) {
+                               spl->i1 = i1;
+                               spl->i2 = i2;
+                               spl->min_lo = spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               /*
+                * We need to extent the diagonal "domain" by one. If the next
+                * values exits the box boundaries we need to change it in the
+                * opposite direction because (max - min) must be a power of two.
+                * Also we initialize the extenal K value to -1 so that we can
+                * avoid extra conditions check inside the core loop.
+                */
+               if (bmin > dmin)
+                       kvdb[--bmin - 1] = XDL_LINE_MAX;
+               else
+                       ++bmin;
+               if (bmax < dmax)
+                       kvdb[++bmax + 1] = XDL_LINE_MAX;
+               else
+                       --bmax;
+
+               for (d = bmax; d >= bmin; d -= 2) {
+                       if (kvdb[d - 1] < kvdb[d + 1])
+                               i1 = kvdb[d - 1];
+                       else
+                               i1 = kvdb[d + 1] - 1;
+                       prev1 = i1;
+                       i2 = i1 - d;
+                       for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--);
+                       if (prev1 - i1 > xenv->snake_cnt)
+                               got_snake = 1;
+                       kvdb[d] = i1;
+                       if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) {
+                               spl->i1 = i1;
+                               spl->i2 = i2;
+                               spl->min_lo = spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               if (need_min)
+                       continue;
+
+               /*
+                * If the edit cost is above the heuristic trigger and if
+                * we got a good snake, we sample current diagonals to see
+                * if some of the, have reached an "interesting" path. Our
+                * measure is a function of the distance from the diagonal
+                * corner (i1 + i2) penalized with the distance from the
+                * mid diagonal itself. If this value is above the current
+                * edit cost times a magic factor (XDL_K_HEUR) we consider
+                * it interesting.
+                */
+               if (got_snake && ec > xenv->heur_min) {
+                       for (best = 0, d = fmax; d >= fmin; d -= 2) {
+                               dd = d > fmid ? d - fmid: fmid - d;
+                               i1 = kvdf[d];
+                               i2 = i1 - d;
+                               v = (i1 - off1) + (i2 - off2) - dd;
+
+                               if (v > XDL_K_HEUR * ec && v > best &&
+                                   off1 + xenv->snake_cnt <= i1 && i1 < lim1 &&
+                                   off2 + xenv->snake_cnt <= i2 && i2 < lim2) {
+                                       for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++)
+                                               if (k == xenv->snake_cnt) {
+                                                       best = v;
+                                                       spl->i1 = i1;
+                                                       spl->i2 = i2;
+                                                       break;
+                                               }
+                               }
+                       }
+                       if (best > 0) {
+                               spl->min_lo = 1;
+                               spl->min_hi = 0;
+                               return ec;
+                       }
+
+                       for (best = 0, d = bmax; d >= bmin; d -= 2) {
+                               dd = d > bmid ? d - bmid: bmid - d;
+                               i1 = kvdb[d];
+                               i2 = i1 - d;
+                               v = (lim1 - i1) + (lim2 - i2) - dd;
+
+                               if (v > XDL_K_HEUR * ec && v > best &&
+                                   off1 < i1 && i1 <= lim1 - xenv->snake_cnt &&
+                                   off2 < i2 && i2 <= lim2 - xenv->snake_cnt) {
+                                       for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++)
+                                               if (k == xenv->snake_cnt - 1) {
+                                                       best = v;
+                                                       spl->i1 = i1;
+                                                       spl->i2 = i2;
+                                                       break;
+                                               }
+                               }
+                       }
+                       if (best > 0) {
+                               spl->min_lo = 0;
+                               spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               /*
+                * Enough is enough. We spent too much time here and now we collect
+                * the furthest reaching path using the (i1 + i2) measure.
+                */
+               if (ec >= xenv->mxcost) {
+                       long fbest, fbest1, bbest, bbest1;
+
+                       fbest = fbest1 = -1;
+                       for (d = fmax; d >= fmin; d -= 2) {
+                               i1 = XDL_MIN(kvdf[d], lim1);
+                               i2 = i1 - d;
+                               if (lim2 < i2)
+                                       i1 = lim2 + d, i2 = lim2;
+                               if (fbest < i1 + i2) {
+                                       fbest = i1 + i2;
+                                       fbest1 = i1;
+                               }
+                       }
+
+                       bbest = bbest1 = XDL_LINE_MAX;
+                       for (d = bmax; d >= bmin; d -= 2) {
+                               i1 = XDL_MAX(off1, kvdb[d]);
+                               i2 = i1 - d;
+                               if (i2 < off2)
+                                       i1 = off2 + d, i2 = off2;
+                               if (i1 + i2 < bbest) {
+                                       bbest = i1 + i2;
+                                       bbest1 = i1;
+                               }
+                       }
+
+                       if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) {
+                               spl->i1 = fbest1;
+                               spl->i2 = fbest - fbest1;
+                               spl->min_lo = 1;
+                               spl->min_hi = 0;
+                       } else {
+                               spl->i1 = bbest1;
+                               spl->i2 = bbest - bbest1;
+                               spl->min_lo = 0;
+                               spl->min_hi = 1;
+                       }
+                       return ec;
+               }
+       }
+
+       return -1;
+}
+
+
+/*
+ * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling
+ * the box splitting function. Note that the real job (marking changed lines)
+ * is done in the two boundary reaching checks.
+ */
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+                diffdata_t *dd2, long off2, long lim2,
+                long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
+       unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+
+       /*
+        * Shrink the box by walking through each diagonal snake (SW and NE).
+        */
+       for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++);
+       for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--);
+
+       /*
+        * If one dimension is empty, then all records on the other one must
+        * be obviously changed.
+        */
+       if (off1 == lim1) {
+               char *rchg2 = dd2->rchg;
+               long *rindex2 = dd2->rindex;
+
+               for (; off2 < lim2; off2++)
+                       rchg2[rindex2[off2]] = 1;
+       } else if (off2 == lim2) {
+               char *rchg1 = dd1->rchg;
+               long *rindex1 = dd1->rindex;
+
+               for (; off1 < lim1; off1++)
+                       rchg1[rindex1[off1]] = 1;
+       } else {
+               long ec;
+               xdpsplit_t spl;
+               spl.i1 = spl.i2 = 0;
+
+               /*
+                * Divide ...
+                */
+               if ((ec = xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+                                   need_min, &spl, xenv)) < 0) {
+
+                       return -1;
+               }
+
+               /*
+                * ... et Impera.
+                */
+               if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2,
+                                kvdf, kvdb, spl.min_lo, xenv) < 0 ||
+                   xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2,
+                                kvdf, kvdb, spl.min_hi, xenv) < 0) {
+
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *xe) {
+       long ndiags;
+       long *kvd, *kvdf, *kvdb;
+       xdalgoenv_t xenv;
+       diffdata_t dd1, dd2;
+
+       if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
+
+               return -1;
+       }
+
+       /*
+        * Allocate and setup K vectors to be used by the differential algorithm.
+        * One is to store the forward path and one to store the backward path.
+        */
+       ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3;
+       if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) {
+
+               xdl_free_env(xe);
+               return -1;
+       }
+       kvdf = kvd;
+       kvdb = kvdf + ndiags;
+       kvdf += xe->xdf2.nreff + 1;
+       kvdb += xe->xdf2.nreff + 1;
+
+       xenv.mxcost = xdl_bogosqrt(ndiags);
+       if (xenv.mxcost < XDL_MAX_COST_MIN)
+               xenv.mxcost = XDL_MAX_COST_MIN;
+       xenv.snake_cnt = XDL_SNAKE_CNT;
+       xenv.heur_min = XDL_HEUR_MIN_COST;
+
+       dd1.nrec = xe->xdf1.nreff;
+       dd1.ha = xe->xdf1.ha;
+       dd1.rchg = xe->xdf1.rchg;
+       dd1.rindex = xe->xdf1.rindex;
+       dd2.nrec = xe->xdf2.nreff;
+       dd2.ha = xe->xdf2.ha;
+       dd2.rchg = xe->xdf2.rchg;
+       dd2.rindex = xe->xdf2.rindex;
+
+       if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+                        kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) {
+
+               xdl_free(kvd);
+               xdl_free_env(xe);
+               return -1;
+       }
+
+       xdl_free(kvd);
+
+       return 0;
+}
+
+
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) {
+       xdchange_t *xch;
+
+       if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t))))
+               return NULL;
+
+       xch->next = xscr;
+       xch->i1 = i1;
+       xch->i2 = i2;
+       xch->chg1 = chg1;
+       xch->chg2 = chg2;
+
+       return xch;
+}
+
+
+static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo) {
+       long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
+       char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
+       xrecord_t **recs = xdf->recs;
+
+       /*
+        * This is the same of what GNU diff does. Move back and forward
+        * change groups for a consistent and pretty diff output. This also
+        * helps in finding joineable change groups and reduce the diff size.
+        */
+       for (ix = ixo = 0;;) {
+               /*
+                * Find the first changed line in the to-be-compacted file.
+                * We need to keep track of both indexes, so if we find a
+                * changed lines group on the other file, while scanning the
+                * to-be-compacted file, we need to skip it properly. Note
+                * that loops that are testing for changed lines on rchg* do
+                * not need index bounding since the array is prepared with
+                * a zero at position -1 and N.
+                */
+               for (; ix < nrec && !rchg[ix]; ix++)
+                       while (rchgo[ixo++]);
+               if (ix == nrec)
+                       break;
+
+               /*
+                * Record the start of a changed-group in the to-be-compacted file
+                * and find the end of it, on both to-be-compacted and other file
+                * indexes (ix and ixo).
+                */
+               ixs = ix;
+               for (ix++; rchg[ix]; ix++);
+               for (; rchgo[ixo]; ixo++);
+
+               do {
+                       grpsiz = ix - ixs;
+
+                       /*
+                        * If the line before the current change group, is equal to
+                        * the last line of the current change group, shift backward
+                        * the group.
+                        */
+                       while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha &&
+                              XDL_RECMATCH(recs[ixs - 1], recs[ix - 1])) {
+                               rchg[--ixs] = 1;
+                               rchg[--ix] = 0;
+
+                               /*
+                                * This change might have joined two change groups,
+                                * so we try to take this scenario in account by moving
+                                * the start index accordingly (and so the other-file
+                                * end-of-group index).
+                                */
+                               for (; rchg[ixs - 1]; ixs--);
+                               while (rchgo[--ixo]);
+                       }
+
+                       /*
+                        * Record the end-of-group position in case we are matched
+                        * with a group of changes in the other file (that is, the
+                        * change record before the enf-of-group index in the other
+                        * file is set).
+                        */
+                       ixref = rchgo[ixo - 1] ? ix: nrec;
+
+                       /*
+                        * If the first line of the current change group, is equal to
+                        * the line next of the current change group, shift forward
+                        * the group.
+                        */
+                       while (ix < nrec && recs[ixs]->ha == recs[ix]->ha &&
+                              XDL_RECMATCH(recs[ixs], recs[ix])) {
+                               rchg[ixs++] = 0;
+                               rchg[ix++] = 1;
+
+                               /*
+                                * This change might have joined two change groups,
+                                * so we try to take this scenario in account by moving
+                                * the start index accordingly (and so the other-file
+                                * end-of-group index). Keep tracking the reference
+                                * index in case we are shifting together with a
+                                * corresponding group of changes in the other file.
+                                */
+                               for (; rchg[ix]; ix++);
+                               while (rchgo[++ixo])
+                                       ixref = ix;
+                       }
+               } while (grpsiz != ix - ixs);
+
+               /*
+                * Try to move back the possibly merged group of changes, to match
+                * the recorded postion in the other file.
+                */
+               while (ixref < ix) {
+                       rchg[--ixs] = 1;
+                       rchg[--ix] = 0;
+                       while (rchgo[--ixo]);
+               }
+       }
+
+       return 0;
+}
+
+
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
+       xdchange_t *cscr = NULL, *xch;
+       char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+       long i1, i2, l1, l2;
+
+       /*
+        * Trivial. Collects "groups" of changes and creates an edit script.
+        */
+       for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+               if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
+                       for (l1 = i1; rchg1[i1 - 1]; i1--);
+                       for (l2 = i2; rchg2[i2 - 1]; i2--);
+
+                       if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) {
+                               xdl_free_script(cscr);
+                               return -1;
+                       }
+                       cscr = xch;
+               }
+
+       *xscr = cscr;
+
+       return 0;
+}
+
+
+void xdl_free_script(xdchange_t *xscr) {
+       xdchange_t *xch;
+
+       while ((xch = xscr) != NULL) {
+               xscr = xscr->next;
+               xdl_free(xch);
+       }
+}
+
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+            xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
+       xdchange_t *xscr;
+       xdfenv_t xe;
+
+       if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
+
+               return -1;
+       }
+       if (xdl_change_compact(&xe.xdf1, &xe.xdf2) < 0 ||
+           xdl_change_compact(&xe.xdf2, &xe.xdf1) < 0 ||
+           xdl_build_script(&xe, &xscr) < 0) {
+
+               xdl_free_env(&xe);
+               return -1;
+       }
+       if (xscr) {
+               if (xdl_emit_diff(&xe, xscr, ecb, xecfg) < 0) {
+
+                       xdl_free_script(xscr);
+                       xdl_free_env(&xe);
+                       return -1;
+               }
+               xdl_free_script(xscr);
+       }
+       xdl_free_env(&xe);
+
+       return 0;
+}
+
diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h
new file mode 100644 (file)
index 0000000..dd8f3c9
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XDIFFI_H)
+#define XDIFFI_H
+
+
+typedef struct s_diffdata {
+       long nrec;
+       unsigned long const *ha;
+       long *rindex;
+       char *rchg;
+} diffdata_t;
+
+typedef struct s_xdalgoenv {
+       long mxcost;
+       long snake_cnt;
+       long heur_min;
+} xdalgoenv_t;
+
+typedef struct s_xdchange {
+       struct s_xdchange *next;
+       long i1, i2;
+       long chg1, chg2;
+} xdchange_t;
+
+
+
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+                diffdata_t *dd2, long off2, long lim2,
+                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_build_script(xdfenv_t *xe, xdchange_t **xscr);
+void xdl_free_script(xdchange_t *xscr);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                 xdemitconf_t const *xecfg);
+
+
+#endif /* #if !defined(XDIFFI_H) */
+
diff --git a/xdiff/xemit.c b/xdiff/xemit.c
new file mode 100644 (file)
index 0000000..ad5bfb1
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  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"
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec);
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb);
+static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg);
+
+
+
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
+
+       *rec = xdf->recs[ri]->ptr;
+
+       return xdf->recs[ri]->size;
+}
+
+
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) {
+       long size, psize = strlen(pre);
+       char const *rec;
+
+       size = xdl_get_rec(xdf, ri, &rec);
+       if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
+
+
+/*
+ * Starting at the passed change atom, find the latest change atom to be included
+ * inside the differential hunk according to the specified configuration.
+ */
+static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
+       xdchange_t *xch, *xchp;
+
+       for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next)
+               if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen)
+                       break;
+
+       return xchp;
+}
+
+
+static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
+
+       /*
+        * Be quite stupid about this for now.  Find a line in the old file
+        * before the start of the hunk (and context) which starts with a
+        * plausible character.
+        */
+
+       const char *rec;
+       long len;
+
+       *ll = 0;
+       while (i-- > 0) {
+               len = xdl_get_rec(xf, i, &rec);
+               if (len > 0 &&
+                   (isalpha((unsigned char)*rec) || /* identifier? */
+                    *rec == '_' ||     /* also identifier? */
+                    *rec == '(' ||     /* lisp defun? */
+                    *rec == '#')) {    /* #define? */
+                       if (len > sz)
+                               len = sz;
+                       if (len && rec[len - 1] == '\n')
+                               len--;
+                       memcpy(buf, rec, len);
+                       *ll = len;
+                       return;
+               }
+       }
+}
+
+
+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];
+       long funclen = 0;
+
+       for (xch = xche = xscr; xch; xch = xche->next) {
+               xche = xdl_get_hunk(xch, xecfg);
+
+               s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
+               s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+
+               lctx = xecfg->ctxlen;
+               lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
+               lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+
+               e1 = xche->i1 + xche->chg1 + lctx;
+               e2 = xche->i2 + xche->chg2 + lctx;
+
+               /*
+                * Emit current hunk header.
+                */
+
+               if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
+                       xdl_find_func(&xe->xdf1, s1, funcbuf,
+                                     sizeof(funcbuf), &funclen);
+               }
+               if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
+                                     funcbuf, funclen, ecb) < 0)
+                       return -1;
+
+               /*
+                * Emit pre-context.
+                */
+               for (; s1 < xch->i1; s1++)
+                       if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+                               return -1;
+
+               for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
+                       /*
+                        * Merge previous with current change atom.
+                        */
+                       for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
+                               if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+                                       return -1;
+
+                       /*
+                        * Removes lines from the first file.
+                        */
+                       for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++)
+                               if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0)
+                                       return -1;
+
+                       /*
+                        * Adds lines from the second file.
+                        */
+                       for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++)
+                               if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0)
+                                       return -1;
+
+                       if (xch == xche)
+                               break;
+                       s1 = xch->i1 + xch->chg1;
+                       s2 = xch->i2 + xch->chg2;
+               }
+
+               /*
+                * Emit post-context.
+                */
+               for (s1 = xche->i1 + xche->chg1; s1 < e1; s1++)
+                       if (xdl_emit_record(&xe->xdf1, s1, " ", ecb) < 0)
+                               return -1;
+       }
+
+       return 0;
+}
+
diff --git a/xdiff/xemit.h b/xdiff/xemit.h
new file mode 100644 (file)
index 0000000..e629417
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XEMIT_H)
+#define XEMIT_H
+
+
+
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                 xdemitconf_t const *xecfg);
+
+
+
+#endif /* #if !defined(XEMIT_H) */
+
diff --git a/xdiff/xinclude.h b/xdiff/xinclude.h
new file mode 100644 (file)
index 0000000..04a9da8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XINCLUDE_H)
+#define XINCLUDE_H
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+
+#include "xmacros.h"
+#include "xdiff.h"
+#include "xtypes.h"
+#include "xutils.h"
+#include "xprepare.h"
+#include "xdiffi.h"
+#include "xemit.h"
+
+
+#endif /* #if !defined(XINCLUDE_H) */
+
diff --git a/xdiff/xmacros.h b/xdiff/xmacros.h
new file mode 100644 (file)
index 0000000..78f0260
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XMACROS_H)
+#define XMACROS_H
+
+
+#define GR_PRIME 0x9e370001UL
+
+
+#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
+#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
+#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
+#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+#define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b)))
+#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
+#define XDL_RECMATCH(r1, r2) ((r1)->size == (r2)->size && memcmp((r1)->ptr, (r2)->ptr, (r1)->size) == 0)
+#define XDL_LE32_PUT(p, v) \
+do { \
+       unsigned char *__p = (unsigned char *) (p); \
+       *__p++ = (unsigned char) (v); \
+       *__p++ = (unsigned char) ((v) >> 8); \
+       *__p++ = (unsigned char) ((v) >> 16); \
+       *__p = (unsigned char) ((v) >> 24); \
+} while (0)
+#define XDL_LE32_GET(p, v) \
+do { \
+       unsigned char const *__p = (unsigned char const *) (p); \
+       (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \
+               ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \
+} while (0)
+
+
+#endif /* #if !defined(XMACROS_H) */
+
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c
new file mode 100644 (file)
index 0000000..add5a75
--- /dev/null
@@ -0,0 +1,465 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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"
+
+
+
+#define XDL_KPDIS_RUN 4
+#define XDL_MAX_EQLIMIT 1024
+
+
+
+typedef struct s_xdlclass {
+       struct s_xdlclass *next;
+       unsigned long ha;
+       char const *line;
+       long size;
+       long idx;
+} xdlclass_t;
+
+typedef struct s_xdlclassifier {
+       unsigned int hbits;
+       long hsize;
+       xdlclass_t **rchash;
+       chastore_t ncha;
+       long count;
+} xdlclassifier_t;
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size);
+static void xdl_free_classifier(xdlclassifier_t *cf);
+static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
+                              xrecord_t *rec);
+static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+                          xdlclassifier_t *cf, xdfile_t *xdf);
+static void xdl_free_ctx(xdfile_t *xdf);
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
+static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2);
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size) {
+       long i;
+
+       cf->hbits = xdl_hashbits((unsigned int) size);
+       cf->hsize = 1 << cf->hbits;
+
+       if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) {
+
+               return -1;
+       }
+       if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) {
+
+               xdl_cha_free(&cf->ncha);
+               return -1;
+       }
+       for (i = 0; i < cf->hsize; i++)
+               cf->rchash[i] = NULL;
+
+       cf->count = 0;
+
+       return 0;
+}
+
+
+static void xdl_free_classifier(xdlclassifier_t *cf) {
+
+       xdl_free(cf->rchash);
+       xdl_cha_free(&cf->ncha);
+}
+
+
+static int xdl_classify_record(xdlclassifier_t *cf, xrecord_t **rhash, unsigned int hbits,
+                              xrecord_t *rec) {
+       long hi;
+       char const *line;
+       xdlclass_t *rcrec;
+
+       line = rec->ptr;
+       hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
+       for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
+               if (rcrec->ha == rec->ha && rcrec->size == rec->size &&
+                   !memcmp(line, rcrec->line, rec->size))
+                       break;
+
+       if (!rcrec) {
+               if (!(rcrec = xdl_cha_alloc(&cf->ncha))) {
+
+                       return -1;
+               }
+               rcrec->idx = cf->count++;
+               rcrec->line = line;
+               rcrec->size = rec->size;
+               rcrec->ha = rec->ha;
+               rcrec->next = cf->rchash[hi];
+               cf->rchash[hi] = rcrec;
+       }
+
+       rec->ha = (unsigned long) rcrec->idx;
+
+       hi = (long) XDL_HASHLONG(rec->ha, hbits);
+       rec->next = rhash[hi];
+       rhash[hi] = rec;
+
+       return 0;
+}
+
+
+static int xdl_prepare_ctx(mmfile_t *mf, long narec, xpparam_t const *xpp,
+                          xdlclassifier_t *cf, xdfile_t *xdf) {
+       unsigned int hbits;
+       long i, nrec, hsize, bsize;
+       unsigned long hav;
+       char const *blk, *cur, *top, *prev;
+       xrecord_t *crec;
+       xrecord_t **recs, **rrecs;
+       xrecord_t **rhash;
+       unsigned long *ha;
+       char *rchg;
+       long *rindex;
+
+       if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) {
+
+               return -1;
+       }
+       if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) {
+
+               xdl_cha_free(&xdf->rcha);
+               return -1;
+       }
+
+       hbits = xdl_hashbits((unsigned int) narec);
+       hsize = 1 << hbits;
+       if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) {
+
+               xdl_free(recs);
+               xdl_cha_free(&xdf->rcha);
+               return -1;
+       }
+       for (i = 0; i < hsize; i++)
+               rhash[i] = NULL;
+
+       nrec = 0;
+       if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
+               for (top = blk + bsize;;) {
+                       if (cur >= top) {
+                               if (!(cur = blk = xdl_mmfile_next(mf, &bsize)))
+                                       break;
+                               top = blk + bsize;
+                       }
+                       prev = cur;
+                       hav = xdl_hash_record(&cur, top);
+                       if (nrec >= narec) {
+                               narec *= 2;
+                               if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) {
+
+                                       xdl_free(rhash);
+                                       xdl_free(recs);
+                                       xdl_cha_free(&xdf->rcha);
+                                       return -1;
+                               }
+                               recs = rrecs;
+                       }
+                       if (!(crec = xdl_cha_alloc(&xdf->rcha))) {
+
+                               xdl_free(rhash);
+                               xdl_free(recs);
+                               xdl_cha_free(&xdf->rcha);
+                               return -1;
+                       }
+                       crec->ptr = prev;
+                       crec->size = (long) (cur - prev);
+                       crec->ha = hav;
+                       recs[nrec++] = crec;
+
+                       if (xdl_classify_record(cf, rhash, hbits, crec) < 0) {
+
+                               xdl_free(rhash);
+                               xdl_free(recs);
+                               xdl_cha_free(&xdf->rcha);
+                               return -1;
+                       }
+               }
+       }
+
+       if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) {
+
+               xdl_free(rhash);
+               xdl_free(recs);
+               xdl_cha_free(&xdf->rcha);
+               return -1;
+       }
+       memset(rchg, 0, (nrec + 2) * sizeof(char));
+
+       if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) {
+
+               xdl_free(rchg);
+               xdl_free(rhash);
+               xdl_free(recs);
+               xdl_cha_free(&xdf->rcha);
+               return -1;
+       }
+       if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) {
+
+               xdl_free(rindex);
+               xdl_free(rchg);
+               xdl_free(rhash);
+               xdl_free(recs);
+               xdl_cha_free(&xdf->rcha);
+               return -1;
+       }
+
+       xdf->nrec = nrec;
+       xdf->recs = recs;
+       xdf->hbits = hbits;
+       xdf->rhash = rhash;
+       xdf->rchg = rchg + 1;
+       xdf->rindex = rindex;
+       xdf->nreff = 0;
+       xdf->ha = ha;
+       xdf->dstart = 0;
+       xdf->dend = nrec - 1;
+
+       return 0;
+}
+
+
+static void xdl_free_ctx(xdfile_t *xdf) {
+
+       xdl_free(xdf->rhash);
+       xdl_free(xdf->rindex);
+       xdl_free(xdf->rchg - 1);
+       xdl_free(xdf->ha);
+       xdl_free(xdf->recs);
+       xdl_cha_free(&xdf->rcha);
+}
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+                   xdfenv_t *xe) {
+       long enl1, enl2;
+       xdlclassifier_t cf;
+
+       enl1 = xdl_guess_lines(mf1) + 1;
+       enl2 = xdl_guess_lines(mf2) + 1;
+
+       if (xdl_init_classifier(&cf, enl1 + enl2 + 1) < 0) {
+
+               return -1;
+       }
+
+       if (xdl_prepare_ctx(mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+               xdl_free_classifier(&cf);
+               return -1;
+       }
+       if (xdl_prepare_ctx(mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+               xdl_free_ctx(&xe->xdf1);
+               xdl_free_classifier(&cf);
+               return -1;
+       }
+
+       xdl_free_classifier(&cf);
+
+       if (xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) {
+
+               xdl_free_ctx(&xe->xdf2);
+               xdl_free_ctx(&xe->xdf1);
+               return -1;
+       }
+
+       return 0;
+}
+
+
+void xdl_free_env(xdfenv_t *xe) {
+
+       xdl_free_ctx(&xe->xdf2);
+       xdl_free_ctx(&xe->xdf1);
+}
+
+
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
+       long r, rdis0, rpdis0, rdis1, rpdis1;
+
+       /*
+        * Scans the lines before 'i' to find a run of lines that either
+        * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1).
+        * Note that we always call this function with dis[i] > 1, so the
+        * current line (i) is already a multimatch line.
+        */
+       for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
+               if (!dis[i - r])
+                       rdis0++;
+               else if (dis[i - r] == 2)
+                       rpdis0++;
+               else
+                       break;
+       }
+       /*
+        * If the run before the line 'i' found only multimatch lines, we
+        * return 0 and hence we don't make the current line (i) discarded.
+        * We want to discard multimatch lines only when they appear in the
+        * middle of runs with nomatch lines (dis[j] == 0).
+        */
+       if (rdis0 == 0)
+               return 0;
+       for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
+               if (!dis[i + r])
+                       rdis1++;
+               else if (dis[i + r] == 2)
+                       rpdis1++;
+               else
+                       break;
+       }
+       /*
+        * If the run after the line 'i' found only multimatch lines, we
+        * return 0 and hence we don't make the current line (i) discarded.
+        */
+       if (rdis1 == 0)
+               return 0;
+       rdis1 += rdis0;
+       rpdis1 += rpdis0;
+
+       return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
+}
+
+
+/*
+ * Try to reduce the problem complexity, discard records that have no
+ * matches on the other file. Also, lines that have multiple matches
+ * might be potentially discarded if they happear in a run of discardable.
+ */
+static int xdl_cleanup_records(xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, nm, rhi, nreff, mlim;
+       unsigned long hav;
+       xrecord_t **recs;
+       xrecord_t *rec;
+       char *dis, *dis1, *dis2;
+
+       if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
+
+               return -1;
+       }
+       memset(dis, 0, xdf1->nrec + xdf2->nrec + 2);
+       dis1 = dis;
+       dis2 = dis1 + xdf1->nrec + 1;
+
+       if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
+       for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+               hav = (*recs)->ha;
+               rhi = (long) XDL_HASHLONG(hav, xdf2->hbits);
+               for (nm = 0, rec = xdf2->rhash[rhi]; rec; rec = rec->next)
+                       if (rec->ha == hav && ++nm == mlim)
+                               break;
+               dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+       }
+
+       if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
+       for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+               hav = (*recs)->ha;
+               rhi = (long) XDL_HASHLONG(hav, xdf1->hbits);
+               for (nm = 0, rec = xdf1->rhash[rhi]; rec; rec = rec->next)
+                       if (rec->ha == hav && ++nm == mlim)
+                               break;
+               dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+       }
+
+       for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+            i <= xdf1->dend; i++, recs++) {
+               if (dis1[i] == 1 ||
+                   (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
+                       xdf1->rindex[nreff] = i;
+                       xdf1->ha[nreff] = (*recs)->ha;
+                       nreff++;
+               } else
+                       xdf1->rchg[i] = 1;
+       }
+       xdf1->nreff = nreff;
+
+       for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+            i <= xdf2->dend; i++, recs++) {
+               if (dis2[i] == 1 ||
+                   (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
+                       xdf2->rindex[nreff] = i;
+                       xdf2->ha[nreff] = (*recs)->ha;
+                       nreff++;
+               } else
+                       xdf2->rchg[i] = 1;
+       }
+       xdf2->nreff = nreff;
+
+       xdl_free(dis);
+
+       return 0;
+}
+
+
+/*
+ * Early trim initial and terminal matching records.
+ */
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, lim;
+       xrecord_t **recs1, **recs2;
+
+       recs1 = xdf1->recs;
+       recs2 = xdf2->recs;
+       for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+            i++, recs1++, recs2++)
+               if ((*recs1)->ha != (*recs2)->ha)
+                       break;
+
+       xdf1->dstart = xdf2->dstart = i;
+
+       recs1 = xdf1->recs + xdf1->nrec - 1;
+       recs2 = xdf2->recs + xdf2->nrec - 1;
+       for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
+               if ((*recs1)->ha != (*recs2)->ha)
+                       break;
+
+       xdf1->dend = xdf1->nrec - i - 1;
+       xdf2->dend = xdf2->nrec - i - 1;
+
+       return 0;
+}
+
+
+static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) {
+
+       if (xdl_trim_ends(xdf1, xdf2) < 0 ||
+           xdl_cleanup_records(xdf1, xdf2) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
+
diff --git a/xdiff/xprepare.h b/xdiff/xprepare.h
new file mode 100644 (file)
index 0000000..344c569
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XPREPARE_H)
+#define XPREPARE_H
+
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+                   xdfenv_t *xe);
+void xdl_free_env(xdfenv_t *xe);
+
+
+
+#endif /* #if !defined(XPREPARE_H) */
+
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h
new file mode 100644 (file)
index 0000000..3593a66
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XTYPES_H)
+#define XTYPES_H
+
+
+
+typedef struct s_chanode {
+       struct s_chanode *next;
+       long icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+       chanode_t *head, *tail;
+       long isize, nsize;
+       chanode_t *ancur;
+       chanode_t *sncur;
+       long scurr;
+} chastore_t;
+
+typedef struct s_xrecord {
+       struct s_xrecord *next;
+       char const *ptr;
+       long size;
+       unsigned long ha;
+} xrecord_t;
+
+typedef struct s_xdfile {
+       chastore_t rcha;
+       long nrec;
+       unsigned int hbits;
+       xrecord_t **rhash;
+       long dstart, dend;
+       xrecord_t **recs;
+       char *rchg;
+       long *rindex;
+       long nreff;
+       unsigned long *ha;
+} xdfile_t;
+
+typedef struct s_xdfenv {
+       xdfile_t xdf1, xdf2;
+} xdfenv_t;
+
+
+
+#endif /* #if !defined(XTYPES_H) */
+
diff --git a/xdiff/xutils.c b/xdiff/xutils.c
new file mode 100644 (file)
index 0000000..21ab8e7
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  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"
+
+
+
+#define XDL_GUESS_NLINES 256
+
+
+
+
+long xdl_bogosqrt(long n) {
+       long i;
+
+       /*
+        * Classical integer square root approximation using shifts.
+        */
+       for (i = 1; n > 0; n >>= 2)
+               i <<= 1;
+
+       return i;
+}
+
+
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+                    xdemitcb_t *ecb) {
+       mmbuffer_t mb[3];
+       int i;
+
+       mb[0].ptr = (char *) pre;
+       mb[0].size = psize;
+       mb[1].ptr = (char *) rec;
+       mb[1].size = size;
+       i = 2;
+
+       if (!size || rec[size-1] != '\n') {
+               mb[2].ptr = "\n\\ No newline at end of file\n";
+               mb[2].size = strlen(mb[2].ptr);
+               i = 3;
+       }
+
+       if (ecb->outf(ecb->priv, mb, i) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size)
+{
+       *size = mmf->size;
+       return mmf->ptr;
+}
+
+
+void *xdl_mmfile_next(mmfile_t *mmf, long *size)
+{
+       return NULL;
+}
+
+
+long xdl_mmfile_size(mmfile_t *mmf)
+{
+       return mmf->size;
+}
+
+
+int xdl_cha_init(chastore_t *cha, long isize, long icount) {
+
+       cha->head = cha->tail = NULL;
+       cha->isize = isize;
+       cha->nsize = icount * isize;
+       cha->ancur = cha->sncur = NULL;
+       cha->scurr = 0;
+
+       return 0;
+}
+
+
+void xdl_cha_free(chastore_t *cha) {
+       chanode_t *cur, *tmp;
+
+       for (cur = cha->head; (tmp = cur) != NULL;) {
+               cur = cur->next;
+               xdl_free(tmp);
+       }
+}
+
+
+void *xdl_cha_alloc(chastore_t *cha) {
+       chanode_t *ancur;
+       void *data;
+
+       if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) {
+               if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) {
+
+                       return NULL;
+               }
+               ancur->icurr = 0;
+               ancur->next = NULL;
+               if (cha->tail)
+                       cha->tail->next = ancur;
+               if (!cha->head)
+                       cha->head = ancur;
+               cha->tail = ancur;
+               cha->ancur = ancur;
+       }
+
+       data = (char *) ancur + sizeof(chanode_t) + ancur->icurr;
+       ancur->icurr += cha->isize;
+
+       return data;
+}
+
+
+void *xdl_cha_first(chastore_t *cha) {
+       chanode_t *sncur;
+
+       if (!(cha->sncur = sncur = cha->head))
+               return NULL;
+
+       cha->scurr = 0;
+
+       return (char *) sncur + sizeof(chanode_t) + cha->scurr;
+}
+
+
+void *xdl_cha_next(chastore_t *cha) {
+       chanode_t *sncur;
+
+       if (!(sncur = cha->sncur))
+               return NULL;
+       cha->scurr += cha->isize;
+       if (cha->scurr == sncur->icurr) {
+               if (!(sncur = cha->sncur = sncur->next))
+                       return NULL;
+               cha->scurr = 0;
+       }
+
+       return (char *) sncur + sizeof(chanode_t) + cha->scurr;
+}
+
+
+long xdl_guess_lines(mmfile_t *mf) {
+       long nl = 0, size, tsize = 0;
+       char const *data, *cur, *top;
+
+       if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
+               for (top = data + size; nl < XDL_GUESS_NLINES;) {
+                       if (cur >= top) {
+                               tsize += (long) (cur - data);
+                               if (!(cur = data = xdl_mmfile_next(mf, &size)))
+                                       break;
+                               top = data + size;
+                       }
+                       nl++;
+                       if (!(cur = memchr(cur, '\n', top - cur)))
+                               cur = top;
+                       else
+                               cur++;
+               }
+               tsize += (long) (cur - data);
+       }
+
+       if (nl && tsize)
+               nl = xdl_mmfile_size(mf) / (tsize / nl);
+
+       return nl + 1;
+}
+
+
+unsigned long xdl_hash_record(char const **data, char const *top) {
+       unsigned long ha = 5381;
+       char const *ptr = *data;
+
+       for (; ptr < top && *ptr != '\n'; ptr++) {
+               ha += (ha << 5);
+               ha ^= (unsigned long) *ptr;
+       }
+       *data = ptr < top ? ptr + 1: ptr;
+
+       return ha;
+}
+
+
+unsigned int xdl_hashbits(unsigned int size) {
+       unsigned int val = 1, bits = 0;
+
+       for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++);
+       return bits ? bits: 1;
+}
+
+
+int xdl_num_out(char *out, long val) {
+       char *ptr, *str = out;
+       char buf[32];
+
+       ptr = buf + sizeof(buf) - 1;
+       *ptr = '\0';
+       if (val < 0) {
+               *--ptr = '-';
+               val = -val;
+       }
+       for (; val && ptr > buf; val /= 10)
+               *--ptr = "0123456789"[val % 10];
+       if (*ptr)
+               for (; *ptr; ptr++, str++)
+                       *str = *ptr;
+       else
+               *str++ = '0';
+       *str = '\0';
+
+       return str - out;
+}
+
+
+long xdl_atol(char const *str, char const **next) {
+       long val, base;
+       char const *top;
+
+       for (top = str; XDL_ISDIGIT(*top); top++);
+       if (next)
+               *next = top;
+       for (val = 0, base = 1, top--; top >= str; top--, base *= 10)
+               val += base * (long)(*top - '0');
+       return val;
+}
+
+
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+                     const char *func, long funclen, xdemitcb_t *ecb) {
+       int nb = 0;
+       mmbuffer_t mb;
+       char buf[128];
+
+       memcpy(buf, "@@ -", 4);
+       nb += 4;
+
+       nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1);
+
+       if (c1 != 1) {
+               memcpy(buf + nb, ",", 1);
+               nb += 1;
+
+               nb += xdl_num_out(buf + nb, c1);
+       }
+
+       memcpy(buf + nb, " +", 2);
+       nb += 2;
+
+       nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1);
+
+       if (c2 != 1) {
+               memcpy(buf + nb, ",", 1);
+               nb += 1;
+
+               nb += xdl_num_out(buf + nb, c2);
+       }
+
+       memcpy(buf + nb, " @@", 3);
+       nb += 3;
+       if (func && funclen) {
+               buf[nb++] = ' ';
+               if (funclen > sizeof(buf) - nb - 1)
+                       funclen = sizeof(buf) - nb - 1;
+               memcpy(buf + nb, func, funclen);
+               nb += funclen;
+       }
+       buf[nb++] = '\n';
+
+       mb.ptr = buf;
+       mb.size = nb;
+       if (ecb->outf(ecb->priv, &mb, 1) < 0)
+               return -1;
+
+       return 0;
+}
+
diff --git a/xdiff/xutils.h b/xdiff/xutils.h
new file mode 100644 (file)
index 0000000..ea38ee9
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  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>
+ *
+ */
+
+#if !defined(XUTILS_H)
+#define XUTILS_H
+
+
+long xdl_bogosqrt(long n);
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+                    xdemitcb_t *ecb);
+int xdl_cha_init(chastore_t *cha, long isize, long icount);
+void xdl_cha_free(chastore_t *cha);
+void *xdl_cha_alloc(chastore_t *cha);
+void *xdl_cha_first(chastore_t *cha);
+void *xdl_cha_next(chastore_t *cha);
+long xdl_guess_lines(mmfile_t *mf);
+unsigned long xdl_hash_record(char const **data, char const *top);
+unsigned int xdl_hashbits(unsigned int size);
+int xdl_num_out(char *out, long val);
+long xdl_atol(char const *str, char const **next);
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+                     const char *func, long funclen, xdemitcb_t *ecb);
+
+
+
+#endif /* #if !defined(XUTILS_H) */
+